1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
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.
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.
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
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 # ____________________________________________________________
26 from types import FunctionType
29 #from pickle import loads, dumps
31 from tiramisu.error import ConfigError, ConflictError, ValueWarning
32 from tiramisu.setting import groups, multitypes
33 from tiramisu.i18n import _
34 from tiramisu.autolib import carry_out_calculation
36 #FIXME : need storage...
37 from tiramisu.storage.sqlalchemy.option import StorageBase, StorageOptionDescription
38 from sqlalchemy.ext.declarative import declarative_base, declared_attr
40 name_regexp = re.compile(r'^\d+')
41 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
42 'make_dict', 'unwrap_from_path', 'read_only',
43 'read_write', 'getowner', 'set_contexts')
47 "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
48 if not isinstance(name, str):
50 if re.match(name_regexp, name) is None and not name.startswith('_') \
51 and name not in forbidden_names \
52 and not name.startswith('impl_') \
53 and not name.startswith('cfgimpl_'):
57 #____________________________________________________________
59 class Base(StorageBase):
60 def __init__(self, name, doc, default=None, default_multi=None,
61 requires=None, multi=False, callback=None,
62 callback_params=None, validator=None, validator_params=None,
63 properties=None, warnings_only=False, choice_values=None,
64 choice_open_values=None):
65 if not valid_name(name):
66 raise ValueError(_("invalid name: {0} for option").format(name))
68 self.impl_set_information('doc', doc)
69 requires = validate_requires_arg(requires, self._name)
70 if requires is not None:
71 for values in requires.values():
72 for require in values.values():
73 self._add_require(require)
74 if not multi and default_multi is not None:
75 raise ValueError(_("a default_multi is set whereas multi is False"
76 " in option: {0}").format(name))
77 if default_multi is not None:
79 self._validate(default_multi)
80 except ValueError as err:
81 raise ValueError(_("invalid default_multi value {0} "
82 "for option {1}: {2}").format(
83 str(default_multi), name, err))
88 self._multitype = multitypes.default
89 self._default_multi = default_multi
90 if callback is not None and ((not multi and (default is not None or
91 default_multi is not None))
92 or (multi and (default != [] or
93 default_multi is not None))
95 raise ValueError(_("default value not allowed if option: {0} "
96 "is calculated").format(name))
97 if properties is None:
99 if not isinstance(properties, tuple):
100 raise TypeError(_('invalid properties type {0} for {1},'
101 ' must be a tuple').format(
104 if validator is not None:
105 validate_callback(validator, validator_params, 'validator')
106 self._validator = validator
107 if validator_params is not None:
108 for key, values in validator_params.items():
109 self._add_validator(key, values)
110 if callback is None and callback_params is not None:
111 raise ValueError(_("params defined for a callback function but "
112 "no callback defined"
113 " yet for option {0}").format(name))
114 if callback is not None:
115 validate_callback(callback, callback_params, 'callback')
116 self._callback = callback
117 if callback_params is not None:
118 for key, values in callback_params.items():
119 self._add_callback(key, values)
120 if requires is not None and properties is not tuple():
121 set_forbidden_properties = set(properties) & set(requires.keys())
122 if set_forbidden_properties != frozenset():
123 raise ValueError('conflict: properties already set in '
124 'requirement {0}'.format(
125 list(set_forbidden_properties)))
126 if choice_values is not None:
127 self._choice_values = choice_values
128 if choice_open_values is not None:
129 self._choice_open_values = choice_open_values
130 self.impl_validate(default)
131 if multi and default is None:
134 self._default = default
135 for prop in properties:
136 self._properties.append(self._get_property_object(prop))
137 self._warnings_only = warnings_only
138 return super(Base, self).__init__()
141 class BaseOption(Base):
142 """This abstract base class stands for attribute access
143 in options that have to be set only once, it is of course done in the
146 #__slots__ = ('_name', '_requires', '_properties', '_readonly',
147 # '_calc_properties', '_impl_informations',
148 # '_state_readonly', '_state_requires', '_stated')
150 # ____________________________________________________________
152 def _impl_convert_requires(self, descr, load=False):
153 """export of the requires during the serialization process
155 :type descr: :class:`tiramisu.option.OptionDescription`
156 :param load: `True` if we are at the init of the option description
159 if not load and self._requires is None:
160 self._state_requires = None
161 elif load and self._state_requires is None:
162 self._requires = None
163 del(self._state_requires)
166 _requires = self._state_requires
168 _requires = self._requires
170 for requires in _requires:
172 for require in requires:
174 new_require = [descr.impl_get_opt_by_path(require[0])]
176 new_require = [descr.impl_get_path_by_opt(require[0])]
177 new_require.extend(require[1:])
178 new_requires.append(tuple(new_require))
179 new_value.append(tuple(new_requires))
181 del(self._state_requires)
182 self._requires = new_value
184 self._state_requires = new_value
187 def _impl_getstate(self, descr):
188 """the under the hood stuff that need to be done
189 before the serialization.
191 :param descr: the parent :class:`tiramisu.option.OptionDescription`
194 for func in dir(self):
195 if func.startswith('_impl_convert_'):
196 getattr(self, func)(descr)
197 self._state_readonly = self._readonly
199 def __getstate__(self, stated=True):
200 """special method to enable the serialization with pickle
201 Usualy, a `__getstate__` method does'nt need any parameter,
202 but somme under the hood stuff need to be done before this action
204 :parameter stated: if stated is `True`, the serialization protocol
205 can be performed, not ready yet otherwise
206 :parameter type: bool
210 except AttributeError:
211 raise SystemError(_('cannot serialize Option, '
212 'only in OptionDescription'))
214 for subclass in self.__class__.__mro__:
215 if subclass is not object:
216 slots.update(subclass.__slots__)
217 slots -= frozenset(['_cache_paths', '_cache_consistencies',
221 # remove variable if save variable converted
222 # in _state_xxxx variable
223 if '_state' + slot not in slots:
224 if slot.startswith('_state'):
226 states[slot] = getattr(self, slot)
227 # remove _state_xxx variable
228 self.__delattr__(slot)
231 states[slot] = getattr(self, slot)
232 except AttributeError:
235 del(states['_stated'])
239 def _impl_setstate(self, descr):
240 """the under the hood stuff that need to be done
241 before the serialization.
243 :type descr: :class:`tiramisu.option.OptionDescription`
245 for func in dir(self):
246 if func.startswith('_impl_convert_'):
247 getattr(self, func)(descr, load=True)
249 self._readonly = self._state_readonly
250 del(self._state_readonly)
252 except AttributeError:
255 def __setstate__(self, state):
256 """special method that enables us to serialize (pickle)
258 Usualy, a `__setstate__` method does'nt need any parameter,
259 but somme under the hood stuff need to be done before this action
261 :parameter state: a dict is passed to the loads, it is the attributes
262 of the options object
265 for key, value in state.items():
266 setattr(self, key, value)
268 def impl_getname(self):
272 class Option(BaseOption):
274 Abstract base class for configuration option's.
276 Reminder: an Option object is **not** a container for the value.
278 # __slots__ = ('_multi', '_validator', '_default_multi', '_default',
279 # '_state_callback', '_callback', '_multitype',
280 # '_consistencies', '_warnings_only', '_master_slaves',
281 # '_state_consistencies', '__weakref__')
284 def __init__(self, name, doc, default=None, default_multi=None,
285 requires=None, multi=False, callback=None,
286 callback_params=None, validator=None, validator_params=None,
287 properties=None, warnings_only=False, choice_values=None,
288 choice_open_values=None):
290 :param name: the option's name
291 :param doc: the option's description
292 :param default: specifies the default value of the option,
293 for a multi : ['bla', 'bla', 'bla']
294 :param default_multi: 'bla' (used in case of a reset to default only at
296 :param requires: is a list of names of options located anywhere
297 in the configuration.
298 :param multi: if true, the option's value is a list
299 :param callback: the name of a function. If set, the function's output
300 is responsible of the option's value
301 :param callback_params: the callback's parameter
302 :param validator: the name of a function which stands for a custom
303 validation of the value
304 :param validator_params: the validator's parameters
305 :param properties: tuple of default properties
306 :param warnings_only: _validator and _consistencies don't raise if True
307 Values()._warning contain message
310 super(Option, self).__init__(name, doc, default, default_multi,
311 requires, multi, callback,
312 callback_params, validator,
313 validator_params, properties,
314 warnings_only, choice_values,
317 #def __setattr__(self, name, value):
318 # """set once and only once some attributes in the option,
319 # like `_name`. `_name` cannot be changed one the option and
320 # pushed in the :class:`tiramisu.option.OptionDescription`.
322 # if the attribute `_readonly` is set to `True`, the option is
323 # "frozen" (which has noting to do with the high level "freeze"
324 # propertie or "read_only" property)
326 # #FIXME ne devrait pas pouvoir redefinir _option
327 # if not name == '_option':
328 # is_readonly = False
329 # # never change _name
330 # if name == '_name':
333 # #so _name is already set
337 # elif name != '_readonly':
339 # if self._readonly is True:
341 # except AttributeError:
342 # self._readonly = False
344 # raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
345 # " read-only").format(
346 # self.__class__.__name__,
349 # object.__setattr__(self, name, value)
351 def impl_getproperties(self):
352 for prop in self._properties:
355 def impl_getrequires(self):
356 return self._requires
358 def _launch_consistency(self, func, option, value, context, index,
360 """Launch consistency now
362 :param func: function name, this name should start with _cons_
364 :param option: option that value is changing
365 :type option: `tiramisu.option.Option`
366 :param value: new value of this option
367 :param context: Config's context, if None, check default value instead
368 :type context: `tiramisu.config.Config`
369 :param index: only for multi option, consistency should be launch for
372 :param all_cons_opts: all options concerne by this consistency
373 :type all_cons_opts: `list` of `tiramisu.option.Option`
375 if context is not None:
376 descr = context.cfgimpl_get_description()
377 #option is also in all_cons_opts
378 if option not in all_cons_opts:
379 raise ConfigError(_('option not in all_cons_opts'))
382 for opt in all_cons_opts:
387 #if context, calculate value, otherwise get default value
388 if context is not None:
389 opt_value = context._getattr(
390 descr.impl_get_path_by_opt(opt), validate=False)
392 opt_value = opt.impl_getdefault()
395 if not self.impl_is_multi() or option == opt:
396 all_cons_vals.append(opt_value)
398 #value is not already set, could be higher index
400 all_cons_vals.append(opt_value[index])
402 #so return if no value
404 getattr(self, func)(all_cons_opts, all_cons_vals)
406 def impl_validate(self, value, context=None, validate=True,
409 :param value: the option's value
410 :param context: Config's context
411 :type context: :class:`tiramisu.config.Config`
412 :param validate: if true enables ``self._validator`` validation
413 :type validate: boolean
414 :param force_no_multi: if multi, value has to be a list
415 not if force_no_multi is True
416 :type force_no_multi: boolean
421 def val_validator(val):
422 if self._validator is not None:
423 class FakeCallbackParamOption(object):
424 def __init__(self, option=None,
425 force_permissive=None,
428 self.force_permissive = force_permissive
431 class FakeCallbackParam(object):
432 def __init__(self, key, params):
435 if self._validator_params != []:
436 validator_params = []
437 validator_params_option = [FakeCallbackParamOption(value=val)]
438 #if '' in self._validator_params:
439 # for param in self._validator_params['']:
440 # if isinstance(param, tuple):
441 # validator_params_option.append(FakeCallbackParamOption(option=param[0], force_permissive=param[1]))
443 # validator_params_option.append(FakeCallbackParamOption(value=param))
444 validator_params.append(FakeCallbackParam('', validator_params_option))
445 for key in self._validator_params:
447 validator_params.append(key)
449 validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=val),)),)
450 # Raise ValueError if not valid
451 carry_out_calculation(self, config=context,
452 callback=self._validator,
453 callback_params=validator_params)
455 def do_validation(_value, _index=None):
460 self._validate(_value)
461 except ValueError as err:
462 raise ValueError(_('invalid value for option {0}: {1}'
463 '').format(self.impl_getname(), err))
465 # valid with self._validator
466 val_validator(_value)
467 # if not context launch consistency validation
468 if context is not None:
469 descr._valid_consistency(self, _value, context, _index)
470 self._second_level_validation(_value)
471 except ValueError as err:
472 msg = _("invalid value for option {0}: {1}").format(
473 self.impl_getname(), err)
474 if self._warnings_only:
475 warnings.warn_explicit(ValueWarning(msg, self),
477 self.__class__.__name__, 0)
479 raise ValueError(msg)
481 # generic calculation
482 if context is not None:
483 descr = context.cfgimpl_get_description()
485 if not self._multi or force_index is not None:
486 do_validation(value, force_index)
488 if not isinstance(value, list):
489 raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
490 for index, val in enumerate(value):
491 do_validation(val, index)
493 def impl_getdefault(self):
494 "accessing the default value"
497 def impl_getdefault_multi(self):
498 "accessing the default value for a multi"
499 return self._default_multi
501 def impl_get_multitype(self):
502 return self._multitype
504 def impl_get_master_slaves(self):
505 return self._master_slaves
507 def impl_is_empty_by_default(self):
508 "no default value has been set yet"
509 if ((not self.impl_is_multi() and self._default is None) or
510 (self.impl_is_multi() and (self._default == []
511 or None in self._default))):
515 def impl_getdoc(self):
516 "accesses the Option's doc"
517 return self.impl_get_information('doc')
519 def impl_has_callback(self):
520 "to know if a callback has been defined or not"
521 if self._callback is None:
526 def impl_get_callback(self):
527 return self._callback, self._callback_params
529 #def impl_getkey(self, value):
532 def impl_is_multi(self):
535 def impl_add_consistency(self, func, *other_opts):
536 """Add consistency means that value will be validate with other_opts
539 :param func: function's name
541 :param other_opts: options used to validate value
542 :type other_opts: `list` of `tiramisu.option.Option`
544 for opt in other_opts:
545 if not isinstance(opt, Option):
546 raise ConfigError(_('consistency should be set with an option'))
548 raise ConfigError(_('cannot add consistency with itself'))
549 if self.impl_is_multi() != opt.impl_is_multi():
550 raise ConfigError(_('every options in consistency should be '
552 func = '_cons_{0}'.format(func)
553 all_cons_opts = tuple([self] + list(other_opts))
554 value = self.impl_getdefault()
555 if value is not None:
556 if self.impl_is_multi():
557 for idx, val in enumerate(value):
558 self._launch_consistency(func, self, val, None,
561 self._launch_consistency(func, self, value, None,
563 self._add_consistency(func, all_cons_opts)
564 self.impl_validate(self.impl_getdefault())
566 def _cons_not_equal(self, opts, vals):
567 for idx_inf, val_inf in enumerate(vals):
568 for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
569 if val_inf == val_sup is not None:
570 raise ValueError(_("same value for {0} and {1}").format(
571 opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
573 def _impl_convert_callbacks(self, descr, load=False):
575 if not load and self._callback is None:
576 self._state_callback = None
577 elif load and self._state_callback is None:
578 self._callback = None
579 del(self._state_callback)
582 callback, callback_params = self._state_callback
584 callback, callback_params = self._callback
585 if callback_params is not None:
587 for key, values in callback_params.items():
590 if isinstance(value, tuple):
592 value = (descr.impl_get_opt_by_path(value[0]),
595 value = (descr.impl_get_path_by_opt(value[0]),
598 cllbck_prms[key] = tuple(vls)
603 del(self._state_callback)
604 self._callback = (callback, cllbck_prms)
606 self._state_callback = (callback, cllbck_prms)
608 # serialize/unserialize
609 def _impl_convert_consistencies(self, descr, load=False):
610 """during serialization process, many things have to be done.
611 one of them is the localisation of the options.
612 The paths are set once for all.
614 :type descr: :class:`tiramisu.option.OptionDescription`
615 :param load: `True` if we are at the init of the option description
618 if not load and self._consistencies is None:
619 self._state_consistencies = None
620 elif load and self._state_consistencies is None:
621 self._consistencies = None
622 del(self._state_consistencies)
625 consistencies = self._state_consistencies
627 consistencies = self._consistencies
628 if isinstance(consistencies, list):
630 for consistency in consistencies:
632 for obj in consistency[1]:
634 values.append(descr.impl_get_opt_by_path(obj))
636 values.append(descr.impl_get_path_by_opt(obj))
637 new_value.append((consistency[0], tuple(values)))
641 for key, _consistencies in consistencies.items():
643 for key_cons, _cons in _consistencies:
648 descr.impl_get_opt_by_path(_con))
651 descr.impl_get_path_by_opt(_con))
652 new_value[key].append((key_cons, tuple(_list_cons)))
654 del(self._state_consistencies)
655 self._consistencies = new_value
657 self._state_consistencies = new_value
659 def _second_level_validation(self, value):
663 class ChoiceOption(Option):
664 """represents a choice out of several objects.
666 The option can also have the value ``None``
669 #__slots__ = ('_values', '_open_values')
670 def __init__(self, name, doc, values, default=None, default_multi=None,
671 requires=None, multi=False, callback=None,
672 callback_params=None, open_values=False, validator=None,
673 validator_params=None, properties=None, warnings_only=False):
675 :param values: is a list of values the option can possibly take
677 if not isinstance(values, tuple):
678 raise TypeError(_('values must be a tuple for {0}').format(name))
679 if open_values not in (True, False):
680 raise TypeError(_('open_values must be a boolean for '
682 super(ChoiceOption, self).__init__(name, doc, default=default,
683 default_multi=default_multi,
685 callback_params=callback_params,
689 validator_params=validator_params,
690 properties=properties,
691 warnings_only=warnings_only,
692 choice_values=values,
693 choice_open_values=open_values)
695 def impl_get_values(self):
696 return self._choice_values
698 def impl_is_openvalues(self):
699 return self._choice_open_values
701 def _validate(self, value):
702 if not self._choice_open_values and not value in self._choice_values:
703 raise ValueError(_('value {0} is not permitted, '
704 'only {1} is allowed'
705 '').format(value, self._choice_values))
708 class BoolOption(Option):
709 "represents a choice between ``True`` and ``False``"
710 # __slots__ = tuple()
712 def _validate(self, value):
713 if not isinstance(value, bool):
714 raise ValueError(_('invalid boolean'))
717 class IntOption(Option):
718 "represents a choice of an integer"
719 # __slots__ = tuple()
721 def _validate(self, value):
722 if not isinstance(value, int):
723 raise ValueError(_('invalid integer'))
726 class FloatOption(Option):
727 "represents a choice of a floating point number"
730 def _validate(self, value):
731 if not isinstance(value, float):
732 raise ValueError(_('invalid float'))
735 class StrOption(Option):
736 "represents the choice of a string"
739 def _validate(self, value):
740 if not isinstance(value, str):
741 raise ValueError(_('invalid string'))
744 if sys.version_info[0] >= 3:
745 #UnicodeOption is same as StrOption in python 3+
746 class UnicodeOption(StrOption):
750 class UnicodeOption(Option):
751 "represents the choice of a unicode string"
755 def _validate(self, value):
756 if not isinstance(value, unicode):
757 raise ValueError(_('invalid unicode'))
760 class SymLinkOption(BaseOption):
761 #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
762 #not return _opt consistencies
763 #_consistencies = None
765 def __init__(self, name, opt):
767 if not isinstance(opt, Option):
768 raise ValueError(_('malformed symlinkoption '
770 'for symlink {0}').format(name))
772 self._readonly = True
776 def __getattr__(self, name):
777 if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
778 return object.__getattr__(self, name)
780 return getattr(self._opt, name)
782 def _impl_getstate(self, descr):
783 super(SymLinkOption, self)._impl_getstate(descr)
784 self._state_opt = descr.impl_get_path_by_opt(self._opt)
786 def _impl_setstate(self, descr):
787 self._opt = descr.impl_get_opt_by_path(self._state_opt)
789 super(SymLinkOption, self)._impl_setstate(descr)
791 def impl_get_information(self, key, default=None):
792 #FIXME ne devrait pas etre utile si ?
793 return self._opt.impl_get_information(key, default)
796 class IPOption(Option):
797 "represents the choice of an ip"
798 #__slots__ = ('_private_only', '_allow_reserved')
799 def __init__(self, name, doc, default=None, default_multi=None,
800 requires=None, multi=False, callback=None,
801 callback_params=None, validator=None, validator_params=None,
802 properties=None, private_only=False, allow_reserved=False,
803 warnings_only=False):
804 self._private_only = private_only
805 self._allow_reserved = allow_reserved
806 super(IPOption, self).__init__(name, doc, default=default,
807 default_multi=default_multi,
809 callback_params=callback_params,
813 validator_params=validator_params,
814 properties=properties,
815 warnings_only=warnings_only)
817 def _validate(self, value):
818 # sometimes an ip term starts with a zero
819 # but this does not fit in some case, for example bind does not like it
820 for val in value.split('.'):
821 if val.startswith("0") and len(val) > 1:
822 raise ValueError(_('invalid IP'))
823 # 'standard' validation
825 IP('{0}/32'.format(value))
827 raise ValueError(_('invalid IP'))
829 def _second_level_validation(self, value):
830 ip = IP('{0}/32'.format(value))
831 if not self._allow_reserved and ip.iptype() == 'RESERVED':
832 raise ValueError(_("invalid IP, mustn't not be in reserved class"))
833 if self._private_only and not ip.iptype() == 'PRIVATE':
834 raise ValueError(_("invalid IP, must be in private class"))
837 class PortOption(Option):
838 """represents the choice of a port
839 The port numbers are divided into three ranges:
840 the well-known ports,
841 the registered ports,
842 and the dynamic or private ports.
843 You can actived this three range.
844 Port number 0 is reserved and can't be used.
845 see: http://en.wikipedia.org/wiki/Port_numbers
847 #__slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
848 def __init__(self, name, doc, default=None, default_multi=None,
849 requires=None, multi=False, callback=None,
850 callback_params=None, validator=None, validator_params=None,
851 properties=None, allow_range=False, allow_zero=False,
852 allow_wellknown=True, allow_registred=True,
853 allow_private=False, warnings_only=False):
854 self._allow_range = allow_range
855 self._min_value = None
856 self._max_value = None
857 ports_min = [0, 1, 1024, 49152]
858 ports_max = [0, 1023, 49151, 65535]
860 for index, allowed in enumerate([allow_zero,
864 if self._min_value is None:
866 self._min_value = ports_min[index]
869 elif allowed and is_finally:
870 raise ValueError(_('inconsistency in allowed range'))
872 self._max_value = ports_max[index]
874 if self._max_value is None:
875 raise ValueError(_('max value is empty'))
877 super(PortOption, self).__init__(name, doc, default=default,
878 default_multi=default_multi,
880 callback_params=callback_params,
884 validator_params=validator_params,
885 properties=properties,
886 warnings_only=warnings_only)
888 def _validate(self, value):
889 if self._allow_range and ":" in str(value):
890 value = str(value).split(':')
892 raise ValueError('invalid part, range must have two values '
894 if not value[0] < value[1]:
895 raise ValueError('invalid port, first port in range must be'
896 ' smaller than the second one')
901 if not self._min_value <= int(val) <= self._max_value:
902 raise ValueError('invalid port, must be an between {0} and {1}'
903 ''.format(self._min_value, self._max_value))
906 class NetworkOption(Option):
907 "represents the choice of a network"
909 def _validate(self, value):
913 raise ValueError(_('invalid network address'))
915 def _second_level_validation(self, value):
917 if ip.iptype() == 'RESERVED':
918 raise ValueError(_("invalid network address, must not be in reserved class"))
921 class NetmaskOption(Option):
922 "represents the choice of a netmask"
925 def _validate(self, value):
927 IP('0.0.0.0/{0}'.format(value))
929 raise ValueError(_('invalid netmask address'))
931 def _cons_network_netmask(self, opts, vals):
932 #opts must be (netmask, network) options
935 self.__cons_netmask(opts, vals[0], vals[1], False)
937 def _cons_ip_netmask(self, opts, vals):
938 #opts must be (netmask, ip) options
941 self.__cons_netmask(opts, vals[0], vals[1], True)
943 def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
945 raise ConfigError(_('invalid len for opts'))
948 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
950 #if cidr == 32, ip same has network
951 if ip.prefixlen() != 32:
953 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
954 make_net=not make_net)
957 msg = _("invalid network {0} ({1}) "
959 " this network is an IP")
962 msg = _("invalid IP {0} ({1}) with netmask {2},"
963 " this IP is a network")
967 msg = _('invalid IP {0} ({1}) with netmask {2}')
969 msg = _('invalid network {0} ({1}) with netmask {2}')
971 raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
975 class BroadcastOption(Option):
978 def _validate(self, value):
980 IP('{0}/32'.format(value))
982 raise ValueError(_('invalid broadcast address'))
984 def _cons_broadcast(self, opts, vals):
986 raise ConfigError(_('invalid len for vals'))
989 broadcast, network, netmask = vals
990 if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
991 raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
992 '({3}) and netmask {4} ({5})').format(
993 broadcast, opts[0].impl_getname(), network,
994 opts[1].impl_getname(), netmask, opts[2].impl_getname()))
997 class DomainnameOption(Option):
998 """represents the choice of a domain name
999 netbios: for MS domain
1000 hostname: to identify the device
1002 fqdn: with tld, not supported yet
1004 #__slots__ = ('_dom_type', '_allow_ip', '_allow_without_dot', '_domain_re')
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, allow_ip=False, type_='domainname',
1010 warnings_only=False, allow_without_dot=False):
1011 if type_ not in ['netbios', 'hostname', 'domainname']:
1012 raise ValueError(_('unknown type_ {0} for hostname').format(type_))
1013 self._dom_type = type_
1014 if allow_ip not in [True, False]:
1015 raise ValueError(_('allow_ip must be a boolean'))
1016 if allow_without_dot not in [True, False]:
1017 raise ValueError(_('allow_without_dot must be a boolean'))
1018 self._allow_ip = allow_ip
1019 self._allow_without_dot = allow_without_dot
1022 extrachar_mandatory = ''
1023 if self._dom_type == 'netbios':
1025 elif self._dom_type == 'hostname':
1027 elif self._dom_type == 'domainname':
1029 if allow_without_dot is False:
1030 extrachar_mandatory = '\.'
1034 self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-{0}]{{,{1}}}{2}){3}$'
1035 ''.format(extrachar, length, extrachar_mandatory, end))
1036 super(DomainnameOption, self).__init__(name, doc, default=default,
1037 default_multi=default_multi,
1039 callback_params=callback_params,
1042 validator=validator,
1043 validator_params=validator_params,
1044 properties=properties,
1045 warnings_only=warnings_only)
1047 def _validate(self, value):
1048 if self._allow_ip is True:
1050 IP('{0}/32'.format(value))
1054 if self._dom_type == 'domainname' and not self._allow_without_dot and \
1056 raise ValueError(_("invalid domainname, must have dot"))
1057 if len(value) > 255:
1058 raise ValueError(_("invalid domainname's length (max 255)"))
1060 raise ValueError(_("invalid domainname's length (min 2)"))
1061 if not self._domain_re.search(value):
1062 raise ValueError(_('invalid domainname'))
1065 class EmailOption(DomainnameOption):
1066 #__slots__ = tuple()
1067 username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1069 def _validate(self, value):
1070 splitted = value.split('@', 1)
1072 username, domain = splitted
1074 raise ValueError(_('invalid email address, should contains one @'
1076 if not self.username_re.search(username):
1077 raise ValueError(_('invalid username in email address'))
1078 super(EmailOption, self)._validate(domain)
1081 class URLOption(DomainnameOption):
1082 #__slots__ = tuple()
1083 proto_re = re.compile(r'(http|https)://')
1084 path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1086 def _validate(self, value):
1087 match = self.proto_re.search(value)
1089 raise ValueError(_('invalid url, should start with http:// or '
1091 value = value[len(match.group(0)):]
1093 splitted = value.split('/', 1)
1095 domain, files = splitted
1100 splitted = domain.split(':', 1)
1102 domain, port = splitted
1105 domain = splitted[0]
1107 if not 0 <= int(port) <= 65535:
1108 raise ValueError(_('invalid url, port must be an between 0 and '
1110 # validate domainname
1111 super(URLOption, self)._validate(domain)
1113 if files is not None and files != '' and not self.path_re.search(files):
1114 raise ValueError(_('invalid url, should ends with filename'))
1117 class FilenameOption(Option):
1118 #__slots__ = tuple()
1119 path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1121 def _validate(self, value):
1122 match = self.path_re.search(value)
1124 raise ValueError(_('invalid filename'))
1127 class OptionDescription(BaseOption, StorageOptionDescription):
1128 """Config's schema (organisation, group) and container of Options
1129 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1131 #_slots = ('_name', '_requires', '_cache_paths', '_group_type',
1132 # '_state_group_type', '_properties', '_children',
1133 # '_cache_consistencies', '_calc_properties', '__weakref__',
1134 # '_readonly', '_impl_informations', '_state_requires',
1135 # '_stated', '_state_readonly')
1137 def __init__(self, name, doc, children, requires=None, properties=None):
1139 :param children: a list of options (including optiondescriptions)
1142 super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
1143 child_names = [child.impl_getname() for child in children]
1144 #better performance like this
1145 valid_child = copy(child_names)
1148 for child in valid_child:
1150 raise ConflictError(_('duplicate option name: '
1151 '{0}').format(child))
1153 for child in children:
1154 if child._parent is not None:
1155 raise ConflictError(_('duplicate option: '
1156 '{0}').format(child))
1157 self._children.append(child) # = (tuple(child_names), tuple(children))
1158 self._cache_paths = None
1159 self._cache_consistencies = None
1160 # the group_type is useful for filtering OptionDescriptions in a config
1161 self._optiondescription_group_type = groups.default
1163 def impl_getproperties(self):
1165 for prop in self._properties:
1168 def impl_getrequires(self):
1170 return self._requires
1172 def impl_getdoc(self):
1173 return self.impl_get_information('doc')
1175 def impl_validate(self, *args):
1179 def __getattr__(self, name):
1181 if name.startswith('_') or name.startswith('impl_'):
1182 return object.__getattribute__(self, name)
1184 #FIXME regression ... devrait etre un query !
1185 for child in self._children:
1186 if child.impl_getname() == name:
1189 #return session.query(child._type).filter_by(id=child.id).first()
1190 #return pouet#self._children[1][self._children[0].index(name)]
1193 raise AttributeError(_('unknown Option {0} '
1194 'in OptionDescription {1}'
1195 '').format(name, self.impl_getname()))
1197 #def impl_getkey(self, config):
1198 # return tuple([child.impl_getkey(getattr(config, child.impl_getname()))
1199 # for child in self.impl_getchildren()])
1201 def impl_getpaths(self, include_groups=False, _currpath=None):
1202 """returns a list of all paths in self, recursively
1203 _currpath should not be provided (helps with recursion)
1205 if _currpath is None:
1208 for option in self.impl_getchildren():
1209 attr = option.impl_getname()
1210 if isinstance(option, OptionDescription):
1212 paths.append('.'.join(_currpath + [attr]))
1213 paths += option.impl_getpaths(include_groups=include_groups,
1214 _currpath=_currpath + [attr])
1216 paths.append('.'.join(_currpath + [attr]))
1219 def impl_getchildren(self):
1220 #FIXME dans la base ??
1221 return self._children
1222 #for child in self._children:
1223 # yield(session.query(child._type).filter_by(id=child.id).first())
1225 def impl_build_cache(self,
1229 _consistencies=None,
1230 force_no_consistencies=False):
1231 if _currpath is None and self._cache_paths is not None:
1234 if _currpath is None:
1237 if not force_no_consistencies:
1241 if cache_path is None:
1244 for option in self.impl_getchildren():
1245 attr = option.impl_getname()
1246 if option.id is None:
1247 raise SystemError(_("an option's id should not be None "
1248 "for {0}").format(option.impl_getname()))
1249 if option.id in cache_option:
1250 raise ConflictError(_('duplicate option: {0}').format(option))
1251 cache_option.append(option.id)
1252 if not force_no_consistencies:
1253 option._readonly = True
1254 cache_path.append(str('.'.join(_currpath + [attr])))
1255 if not isinstance(option, OptionDescription):
1256 if not force_no_consistencies and \
1257 option._consistencies is not []:
1258 for consistency in option._consistencies:
1259 func = consistency.func
1260 all_cons_opts = consistency.options
1261 for opt in all_cons_opts:
1262 _consistencies.setdefault(opt,
1266 _currpath.append(attr)
1267 option.impl_build_cache(cache_path,
1271 force_no_consistencies)
1274 self._cache_paths = (tuple(cache_option), tuple(cache_path))
1275 if not force_no_consistencies:
1276 if _consistencies != {}:
1277 self._cache_consistencies = {}
1278 for opt, cons in _consistencies.items():
1279 if opt.id not in cache_option:
1280 raise ConfigError(_('consistency with option {0} which is not in Config').format(opt.impl_getname()))
1281 self._cache_consistencies[opt] = tuple(cons)
1282 self._readonly = True
1285 # ____________________________________________________________
1286 def impl_set_group_type(self, group_type):
1287 """sets a given group object to an OptionDescription
1289 :param group_type: an instance of `GroupType` or `MasterGroupType`
1290 that lives in `setting.groups`
1292 if self._optiondescription_group_type != groups.default:
1293 raise TypeError(_('cannot change group_type if already set '
1294 '(old {0}, new {1})').format(self._optiondescription_group_type,
1296 if isinstance(group_type, groups.GroupType):
1297 self._optiondescription_group_type = group_type
1298 if isinstance(group_type, groups.MasterGroupType):
1299 #if master (same name has group) is set
1300 #for collect all slaves
1303 for child in self.impl_getchildren():
1304 if isinstance(child, OptionDescription):
1305 raise ValueError(_("master group {0} shall not have "
1306 "a subgroup").format(self.impl_getname()))
1307 if isinstance(child, SymLinkOption):
1308 raise ValueError(_("master group {0} shall not have "
1309 "a symlinkoption").format(self.impl_getname()))
1310 if not child.impl_is_multi():
1311 raise ValueError(_("not allowed option {0} "
1313 ": this option is not a multi"
1314 "").format(child.impl_getname(), self.impl_getname()))
1315 if child._name == self.impl_getname():
1316 child._multitype = multitypes.master
1319 slaves.append(child)
1321 raise ValueError(_('master group with wrong'
1322 ' master name for {0}'
1323 ).format(self.impl_getname()))
1324 #FIXME debut reecriture
1325 ##master_callback, master_callback_params = master.impl_get_callback()
1326 #if master._callback is not None and master._callback[1] is not None:
1327 # for key, callbacks in master._callback[1].items():
1328 # for callbk in callbacks:
1329 # if isinstance(callbk, tuple):
1330 # if callbk[0] in slaves:
1331 # raise ValueError(_("callback of master's option shall "
1332 # "not refered a slave's ones"))
1333 master._master_slaves = tuple(slaves)
1334 for child in self.impl_getchildren():
1336 child._master_slaves = master
1337 child._multitype = multitypes.slave
1339 raise ValueError(_('group_type: {0}'
1340 ' not allowed').format(group_type))
1342 def impl_get_group_type(self):
1343 return getattr(groups, self._optiondescription_group_type)
1345 def _valid_consistency(self, option, value, context, index):
1346 if self._cache_consistencies is None:
1348 #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1349 consistencies = self._cache_consistencies.get(option)
1350 if consistencies is not None:
1351 for func, all_cons_opts in consistencies:
1352 #all_cons_opts[0] is the option where func is set
1353 ret = all_cons_opts[0]._launch_consistency(func, option,
1361 # ____________________________________________________________
1364 def _impl_getstate(self, descr=None):
1365 """enables us to export into a dict
1366 :param descr: parent :class:`tiramisu.option.OptionDescription`
1369 self.impl_build_cache()
1371 super(OptionDescription, self)._impl_getstate(descr)
1372 self._state_group_type = str(self._optiondescription_group_type)
1373 for option in self.impl_getchildren():
1374 option._impl_getstate(descr)
1376 def __getstate__(self):
1377 """special method to enable the serialization with pickle
1381 # the `_state` attribute is a flag that which tells us if
1382 # the serialization can be performed
1384 except AttributeError:
1385 # if cannot delete, _impl_getstate never launch
1386 # launch it recursivement
1387 # _stated prevent __getstate__ launch more than one time
1388 # _stated is delete, if re-serialize, re-lauch _impl_getstate
1389 self._impl_getstate()
1391 return super(OptionDescription, self).__getstate__(stated)
1393 def _impl_setstate(self, descr=None):
1394 """enables us to import from a dict
1395 :param descr: parent :class:`tiramisu.option.OptionDescription`
1398 self._cache_paths = None
1399 self._cache_consistencies = None
1400 self.impl_build_cache(force_no_consistencies=True)
1402 self._optiondescription_group_type = getattr(groups, self._state_group_type)
1403 del(self._state_group_type)
1404 super(OptionDescription, self)._impl_setstate(descr)
1405 for option in self.impl_getchildren():
1406 option._impl_setstate(descr)
1408 def __setstate__(self, state):
1409 super(OptionDescription, self).__setstate__(state)
1412 except AttributeError:
1413 self._impl_setstate()
1416 def validate_requires_arg(requires, name):
1417 """check malformed requirements
1418 and tranform dict to internal tuple
1420 :param requires: have a look at the
1421 :meth:`tiramisu.setting.Settings.apply_requires` method to
1423 the description of the requires dictionary
1425 if requires is None:
1430 # start parsing all requires given by user (has dict)
1431 # transforme it to a tuple
1432 for require in requires:
1433 if not type(require) == dict:
1434 raise ValueError(_("malformed requirements type for option:"
1435 " {0}, must be a dict").format(name))
1436 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1438 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1439 if unknown_keys != frozenset():
1440 raise ValueError('malformed requirements for option: {0}'
1441 ' unknown keys {1}, must only '
1445 # prepare all attributes
1447 option = require['option']
1448 expected = require['expected']
1449 action = require['action']
1451 raise ValueError(_("malformed requirements for option: {0}"
1452 " require must have option, expected and"
1453 " action keys").format(name))
1454 inverse = require.get('inverse', False)
1455 if inverse not in [True, False]:
1456 raise ValueError(_('malformed requirements for option: {0}'
1457 ' inverse must be boolean'))
1458 transitive = require.get('transitive', True)
1459 if transitive not in [True, False]:
1460 raise ValueError(_('malformed requirements for option: {0}'
1461 ' transitive must be boolean'))
1462 same_action = require.get('same_action', True)
1463 if same_action not in [True, False]:
1464 raise ValueError(_('malformed requirements for option: {0}'
1465 ' same_action must be boolean'))
1467 if not isinstance(option, Option):
1468 raise ValueError(_('malformed requirements '
1469 'must be an option in option {0}').format(name))
1470 if option.impl_is_multi():
1471 raise ValueError(_('malformed requirements option {0} '
1472 'should not be a multi').format(name))
1473 if expected is not None:
1475 option._validate(expected)
1476 except ValueError as err:
1477 raise ValueError(_('malformed requirements second argument '
1478 'must be valid for option {0}'
1479 ': {1}').format(name, err))
1480 if action in config_action:
1481 if inverse != config_action[action]:
1482 raise ValueError(_("inconsistency in action types"
1484 " action: {1}").format(name, action))
1486 config_action[action] = inverse
1487 if action not in ret_requires:
1488 ret_requires[action] = {}
1489 if option not in ret_requires[action]:
1490 ret_requires[action][option] = (option, [expected], action,
1491 inverse, transitive, same_action)
1493 ret_requires[action][option][1].append(expected)
1494 ## transform dict to tuple
1496 #for opt_requires in ret_requires.values():
1498 # for require in opt_requires.values():
1499 # ret_action.append((require[0], tuple(require[1]), require[2],
1500 # require[3], require[4], require[5]))
1501 # ret.append(tuple(ret_action))
1505 def validate_callback(callback, callback_params, type_):
1506 if type(callback) != FunctionType:
1507 raise ValueError(_('{0} should be a function').format(type_))
1508 if callback_params is not None:
1509 if not isinstance(callback_params, dict):
1510 raise ValueError(_('{0}_params should be a dict').format(type_))
1511 for key, callbacks in callback_params.items():
1512 if key != '' and len(callbacks) != 1:
1513 raise ValueError(_('{0}_params with key {1} should not have '
1514 'length different to 1').format(type_,
1516 if not isinstance(callbacks, tuple):
1517 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1518 ).format(type_, key))
1519 for callbk in callbacks:
1520 if isinstance(callbk, tuple):
1521 option, force_permissive = callbk
1522 if type_ == 'validator' and not force_permissive:
1523 raise ValueError(_('validator not support tuple'))
1524 if not isinstance(option, Option) and not \
1525 isinstance(option, SymLinkOption):
1526 raise ValueError(_('{0}_params should have an option '
1527 'not a {0} for first argument'
1528 ).format(type_, type(option)))
1529 if force_permissive not in [True, False]:
1530 raise ValueError(_('{0}_params should have a boolean'
1531 ' not a {0} for second argument'
1532 ).format(type_, type(