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 # ____________________________________________________________
25 from copy import copy, deepcopy
26 from types import FunctionType
30 from tiramisu.error import ConflictError, ValueWarning
31 from tiramisu.setting import groups, multitypes
32 from tiramisu.i18n import _
33 from tiramisu.autolib import carry_out_calculation
35 name_regexp = re.compile(r'^\d+')
36 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
37 'make_dict', 'unwrap_from_path', 'read_only',
38 'read_write', 'getowner', 'set_contexts')
42 "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
47 if re.match(name_regexp, name) is None and not name.startswith('_') \
48 and name not in forbidden_names \
49 and not name.startswith('impl_') \
50 and not name.startswith('cfgimpl_'):
54 #____________________________________________________________
58 class BaseOption(object):
59 """This abstract base class stands for attribute access
60 in options that have to be set only once, it is of course done in the
63 __slots__ = ('_name', '_requires', '_properties', '_readonly',
64 '_consistencies', '_calc_properties', '_impl_informations',
65 '_state_consistencies', '_state_readonly', '_state_requires',
68 def __init__(self, name, doc, requires, properties):
69 if not valid_name(name):
70 raise ValueError(_("invalid name: {0} for option").format(name))
72 self._impl_informations = {}
73 self.impl_set_information('doc', doc)
74 self._calc_properties, self._requires = validate_requires_arg(
76 self._consistencies = None
77 if properties is None:
79 if not isinstance(properties, tuple):
80 raise TypeError(_('invalid properties type {0} for {1},'
81 ' must be a tuple').format(
84 if self._calc_properties is not None and properties is not tuple():
85 set_forbidden_properties = set(properties) & self._calc_properties
86 if set_forbidden_properties != frozenset():
87 raise ValueError('conflict: properties already set in '
88 'requirement {0}'.format(
89 list(set_forbidden_properties)))
90 self._properties = properties # 'hidden', 'disabled'...
92 def __setattr__(self, name, value):
93 """set once and only once some attributes in the option,
94 like `_name`. `_name` cannot be changed one the option and
95 pushed in the :class:`tiramisu.option.OptionDescription`.
97 if the attribute `_readonly` is set to `True`, the option is
98 "frozen" (which has noting to do with the high level "freeze"
99 propertie or "read_only" property)
101 if not name.startswith('_state') and name not in ('_cache_paths',
108 #so _name is already set
113 if self._readonly is True:
115 # already readonly and try to re set readonly
116 # don't raise, just exit
119 except AttributeError:
122 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
123 " read-only").format(
124 self.__class__.__name__,
127 object.__setattr__(self, name, value)
130 def impl_set_information(self, key, value):
131 """updates the information's attribute
132 (which is a dictionary)
134 :param key: information's key (ex: "help", "doc"
135 :param value: information's value (ex: "the help string")
137 self._impl_informations[key] = value
139 def impl_get_information(self, key, default=None):
140 """retrieves one information's item
142 :param key: the item string (ex: "help")
144 if key in self._impl_informations:
145 return self._impl_informations[key]
146 elif default is not None:
149 raise ValueError(_("information's item not found: {0}").format(
152 # serialize/unserialize
153 def _impl_convert_consistencies(self, descr, load=False):
154 """during serialization process, many things have to be done.
155 one of them is the localisation of the options.
156 The paths are set once for all.
158 :type descr: :class:`tiramisu.option.OptionDescription`
159 :param load: `True` if we are at the init of the option description
162 if not load and self._consistencies is None:
163 self._state_consistencies = None
164 elif load and self._state_consistencies is None:
165 self._consistencies = None
166 del(self._state_consistencies)
169 consistencies = self._state_consistencies
171 consistencies = self._consistencies
172 if isinstance(consistencies, list):
174 for consistency in consistencies:
176 new_value.append((consistency[0],
177 descr.impl_get_opt_by_path(
180 new_value.append((consistency[0],
181 descr.impl_get_path_by_opt(
186 for key, _consistencies in consistencies.items():
188 for key_cons, _cons in _consistencies:
193 descr.impl_get_opt_by_path(_con))
196 descr.impl_get_path_by_opt(_con))
197 new_value[key].append((key_cons, tuple(_list_cons)))
199 del(self._state_consistencies)
200 self._consistencies = new_value
202 self._state_consistencies = new_value
204 def _impl_convert_requires(self, descr, load=False):
205 """export of the requires during the serialization process
207 :type descr: :class:`tiramisu.option.OptionDescription`
208 :param load: `True` if we are at the init of the option description
211 if not load and self._requires is None:
212 self._state_requires = None
213 elif load and self._state_requires is None:
214 self._requires = None
215 del(self._state_requires)
218 _requires = self._state_requires
220 _requires = self._requires
222 for requires in _requires:
224 for require in requires:
226 new_require = [descr.impl_get_opt_by_path(require[0])]
228 new_require = [descr.impl_get_path_by_opt(require[0])]
229 new_require.extend(require[1:])
230 new_requires.append(tuple(new_require))
231 new_value.append(tuple(new_requires))
233 del(self._state_requires)
234 self._requires = new_value
236 self._state_requires = new_value
239 def _impl_getstate(self, descr):
240 """the under the hood stuff that need to be done
241 before the serialization.
243 :param descr: the parent :class:`tiramisu.option.OptionDescription`
246 for func in dir(self):
247 if func.startswith('_impl_convert_'):
248 getattr(self, func)(descr)
250 self._state_readonly = self._readonly
251 except AttributeError:
254 def __getstate__(self, stated=True):
255 """special method to enable the serialization with pickle
256 Usualy, a `__getstate__` method does'nt need any parameter,
257 but somme under the hood stuff need to be done before this action
259 :parameter stated: if stated is `True`, the serialization protocol
260 can be performed, not ready yet otherwise
261 :parameter type: bool
265 except AttributeError:
266 raise SystemError(_('cannot serialize Option, '
267 'only in OptionDescription'))
269 for subclass in self.__class__.__mro__:
270 if subclass is not object:
271 slots.update(subclass.__slots__)
272 slots -= frozenset(['_cache_paths', '__weakref__'])
275 # remove variable if save variable converted
276 # in _state_xxxx variable
277 if '_state' + slot not in slots:
278 if slot.startswith('_state'):
280 states[slot] = getattr(self, slot)
281 # remove _state_xxx variable
282 self.__delattr__(slot)
285 states[slot] = getattr(self, slot)
286 except AttributeError:
289 del(states['_stated'])
293 def _impl_setstate(self, descr):
294 """the under the hood stuff that need to be done
295 before the serialization.
297 :type descr: :class:`tiramisu.option.OptionDescription`
299 for func in dir(self):
300 if func.startswith('_impl_convert_'):
301 getattr(self, func)(descr, load=True)
303 self._readonly = self._state_readonly
304 del(self._state_readonly)
306 except AttributeError:
309 def __setstate__(self, state):
310 """special method that enables us to serialize (pickle)
312 Usualy, a `__setstate__` method does'nt need any parameter,
313 but somme under the hood stuff need to be done before this action
315 :parameter state: a dict is passed to the loads, it is the attributes
316 of the options object
319 for key, value in state.items():
320 setattr(self, key, value)
323 class Option(BaseOption):
325 Abstract base class for configuration option's.
327 Reminder: an Option object is **not** a container for the value.
329 __slots__ = ('_multi', '_validator', '_default_multi', '_default',
330 '_state_callback', '_callback', '_multitype',
331 '_only_warning', '_master_slaves', '__weakref__')
334 def __init__(self, name, doc, default=None, default_multi=None,
335 requires=None, multi=False, callback=None,
336 callback_params=None, validator=None, validator_params=None,
337 properties=None, only_warning=False):
339 :param name: the option's name
340 :param doc: the option's description
341 :param default: specifies the default value of the option,
342 for a multi : ['bla', 'bla', 'bla']
343 :param default_multi: 'bla' (used in case of a reset to default only at
345 :param requires: is a list of names of options located anywhere
346 in the configuration.
347 :param multi: if true, the option's value is a list
348 :param callback: the name of a function. If set, the function's output
349 is responsible of the option's value
350 :param callback_params: the callback's parameter
351 :param validator: the name of a function which stands for a custom
352 validation of the value
353 :param validator_params: the validator's parameters
354 :param properties: tuple of default properties
355 :param only_warning: _validator and _consistencies don't raise if True
356 Values()._warning contain message
359 super(Option, self).__init__(name, doc, requires, properties)
361 if validator is not None:
362 validate_callback(validator, validator_params, 'validator')
363 self._validator = (validator, validator_params)
365 self._validator = None
366 if not self._multi and default_multi is not None:
367 raise ValueError(_("a default_multi is set whereas multi is False"
368 " in option: {0}").format(name))
369 if default_multi is not None:
371 self._validate(default_multi)
372 except ValueError as err:
373 raise ValueError(_("invalid default_multi value {0} "
374 "for option {1}: {2}").format(
375 str(default_multi), name, err))
376 if callback is not None and (default is not None or
377 default_multi is not None):
378 raise ValueError(_("default value not allowed if option: {0} "
379 "is calculated").format(name))
380 if callback is None and callback_params is not None:
381 raise ValueError(_("params defined for a callback function but "
382 "no callback defined"
383 " yet for option {0}").format(name))
384 if callback is not None:
385 validate_callback(callback, callback_params, 'callback')
386 self._callback = (callback, callback_params)
388 self._callback = None
392 self._multitype = multitypes.default
393 self._default_multi = default_multi
394 self._only_warning = only_warning
395 self.impl_validate(default)
396 self._default = default
398 def _launch_consistency(self, func, opt, vals, context, index, opt_):
399 if context is not None:
400 descr = context.cfgimpl_get_description()
402 #values are for self, search opt_ values
404 if context is not None:
405 path = descr.impl_get_path_by_opt(opt_)
406 values_ = context._getattr(path, validate=False)
408 values_ = opt_.impl_getdefault()
409 if index is not None:
410 #value is not already set, could be higher
412 values_ = values_[index]
416 #values are for opt_, search self values
418 if context is not None:
419 path = descr.impl_get_path_by_opt(self)
420 values = context._getattr(path, validate=False)
422 values = self.impl_getdefault()
423 if index is not None:
424 #value is not already set, could be higher
426 values = values[index]
429 if index is None and self.impl_is_multi():
430 for index in range(0, len(values)):
432 value = values[index]
433 value_ = values_[index]
437 if None not in (value, value_):
438 getattr(self, func)(opt_._name, value, value_)
440 if None not in (values, values_):
441 getattr(self, func)(opt_._name, values, values_)
443 def impl_validate(self, value, context=None, validate=True,
444 force_no_multi=False):
446 :param value: the option's value
447 :param context: Config's context
448 :type context: :class:`tiramisu.config.Config`
449 :param validate: if true enables ``self._validator`` validation
450 :type validate: boolean
451 :param force_no_multi: if multi, value has to be a list
452 not if force_no_multi is True
453 :type force_no_multi: boolean
458 def val_validator(val):
459 if self._validator is not None:
460 if self._validator[1] is not None:
461 validator_params = deepcopy(self._validator[1])
462 if '' in validator_params:
463 lst = list(validator_params[''])
465 validator_params[''] = tuple(lst)
467 validator_params[''] = (val,)
469 validator_params = {'': (val,)}
470 # Raise ValueError if not valid
471 carry_out_calculation(self._name, config=context,
472 callback=self._validator[0],
473 callback_params=validator_params)
475 def do_validation(_value, _index=None):
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 warnings.warn_explicit(ValueWarning(msg, self),
491 self.__class__.__name__, 0)
493 raise ValueError(msg)
495 self._validate(_value)
497 # generic calculation
498 if context is not None:
499 descr = context.cfgimpl_get_description()
502 if not self._multi or force_no_multi:
503 ret = do_validation(value)
505 if not isinstance(value, list):
506 raise ValueError(_("invalid value {0} for option {1} "
507 "which must be a list").format(value,
509 for index, val in enumerate(value):
510 ret_ = do_validation(val, index)
515 def impl_getdefault(self, default_multi=False):
516 "accessing the default value"
517 if not default_multi or not self.impl_is_multi():
520 return self.getdefault_multi()
522 def impl_getdefault_multi(self):
523 "accessing the default value for a multi"
524 return self._default_multi
526 def impl_get_multitype(self):
527 return self._multitype
529 def impl_get_master_slaves(self):
530 return self._master_slaves
532 def impl_is_empty_by_default(self):
533 "no default value has been set yet"
534 if ((not self.impl_is_multi() and self._default is None) or
535 (self.impl_is_multi() and (self._default == []
536 or None in self._default))):
540 def impl_getdoc(self):
541 "accesses the Option's doc"
542 return self.impl_get_information('doc')
544 def impl_has_callback(self):
545 "to know if a callback has been defined or not"
546 if self._callback is None:
551 def impl_getkey(self, value):
554 def impl_is_multi(self):
557 def impl_add_consistency(self, func, opt):
558 if self._consistencies is None:
559 self._consistencies = []
560 if not isinstance(opt, Option):
561 raise ValueError('consistency must be set with an option')
563 raise ValueError('cannot add consistency with itself')
564 if self.impl_is_multi() != opt.impl_is_multi():
565 raise ValueError('options in consistency'
566 ' should be multi in two sides')
567 func = '_cons_{0}'.format(func)
568 self._launch_consistency(func,
570 self.impl_getdefault(),
572 self._consistencies.append((func, opt))
573 self.impl_validate(self.impl_getdefault())
575 def _cons_not_equal(self, optname, value, value_):
577 raise ValueError(_("invalid value {0} for option {1} "
578 "must be different as {2} option"
579 "").format(value, self._name, optname))
581 def _impl_convert_callbacks(self, descr, load=False):
582 if not load and self._callback is None:
583 self._state_callback = None
584 elif load and self._state_callback is None:
585 self._callback = None
586 del(self._state_callback)
589 callback, callback_params = self._state_callback
591 callback, callback_params = self._callback
592 if callback_params is not None:
594 for key, values in callback_params.items():
597 if isinstance(value, tuple):
599 value = (descr.impl_get_opt_by_path(value[0]),
602 value = (descr.impl_get_path_by_opt(value[0]),
605 cllbck_prms[key] = tuple(vls)
610 del(self._state_callback)
611 self._callback = (callback, cllbck_prms)
613 self._state_callback = (callback, cllbck_prms)
615 def _second_level_validation(self, value):
619 class ChoiceOption(Option):
620 """represents a choice out of several objects.
622 The option can also have the value ``None``
625 __slots__ = ('_values', '_open_values')
628 def __init__(self, name, doc, values, default=None, default_multi=None,
629 requires=None, multi=False, callback=None,
630 callback_params=None, open_values=False, validator=None,
631 validator_params=None, properties=None, only_warning=False):
633 :param values: is a list of values the option can possibly take
635 if not isinstance(values, tuple):
636 raise TypeError(_('values must be a tuple for {0}').format(name))
637 self._values = values
638 if open_values not in (True, False):
639 raise TypeError(_('open_values must be a boolean for '
641 self._open_values = open_values
642 super(ChoiceOption, self).__init__(name, doc, default=default,
643 default_multi=default_multi,
645 callback_params=callback_params,
649 validator_params=validator_params,
650 properties=properties,
651 only_warning=only_warning)
653 def impl_get_values(self):
656 def impl_is_openvalues(self):
657 return self._open_values
659 def _validate(self, value):
660 if not self._open_values and not value in self._values:
661 raise ValueError(_('value {0} is not permitted, '
662 'only {1} is allowed'
663 '').format(value, self._values))
666 class BoolOption(Option):
667 "represents a choice between ``True`` and ``False``"
671 def _validate(self, value):
672 if not isinstance(value, bool):
673 raise ValueError(_('value must be a boolean'))
676 class IntOption(Option):
677 "represents a choice of an integer"
681 def _validate(self, value):
682 if not isinstance(value, int):
683 raise ValueError(_('value must be an integer'))
686 class FloatOption(Option):
687 "represents a choice of a floating point number"
691 def _validate(self, value):
692 if not isinstance(value, float):
693 raise ValueError(_('value must be a float'))
696 class StrOption(Option):
697 "represents the choice of a string"
701 def _validate(self, value):
702 if not isinstance(value, str):
703 raise ValueError(_('value must be a string, not '
704 '{0}').format(type(value)))
707 if sys.version_info[0] >= 3:
708 #UnicodeOption is same has StrOption in python 3+
709 class UnicodeOption(StrOption):
713 class UnicodeOption(Option):
714 "represents the choice of a unicode string"
716 _opt_type = 'unicode'
719 def _validate(self, value):
720 if not isinstance(value, unicode):
721 raise ValueError(_('value must be an unicode'))
724 class SymLinkOption(BaseOption):
725 __slots__ = ('_name', '_opt', '_state_opt')
726 _opt_type = 'symlink'
727 #not return _opt consistencies
730 def __init__(self, name, opt):
732 if not isinstance(opt, Option):
733 raise ValueError(_('malformed symlinkoption '
735 'for symlink {0}').format(name))
737 self._readonly = True
739 def __getattr__(self, name):
740 if name in ('_name', '_opt', '_opt_type', '_readonly'):
741 return object.__getattr__(self, name)
743 return getattr(self._opt, name)
745 def _impl_getstate(self, descr):
746 super(SymLinkOption, self)._impl_getstate(descr)
747 self._state_opt = descr.impl_get_path_by_opt(self._opt)
749 def _impl_setstate(self, descr):
750 self._opt = descr.impl_get_opt_by_path(self._state_opt)
752 super(SymLinkOption, self)._impl_setstate(descr)
754 def _impl_convert_consistencies(self, descr, load=False):
756 del(self._state_consistencies)
758 self._state_consistencies = None
761 class IPOption(Option):
762 "represents the choice of an ip"
763 __slots__ = ('_only_private', '_allow_reserved')
766 def __init__(self, name, doc, default=None, default_multi=None,
767 requires=None, multi=False, callback=None,
768 callback_params=None, validator=None, validator_params=None,
769 properties=None, only_private=False, allow_reserved=False,
771 self._only_private = only_private
772 self._allow_reserved = allow_reserved
773 super(IPOption, self).__init__(name, doc, default=default,
774 default_multi=default_multi,
776 callback_params=callback_params,
780 validator_params=validator_params,
781 properties=properties,
782 only_warning=only_warning)
784 def _validate(self, value):
785 IP('{0}/32'.format(value))
787 def _second_level_validation(self, value):
788 ip = IP('{0}/32'.format(value))
789 if not self._allow_reserved and ip.iptype() == 'RESERVED':
790 raise ValueError(_("IP mustn't not be in reserved class"))
791 if self._only_private and not ip.iptype() == 'PRIVATE':
792 raise ValueError(_("IP must be in private class"))
795 class PortOption(Option):
796 """represents the choice of a port
797 The port numbers are divided into three ranges:
798 the well-known ports,
799 the registered ports,
800 and the dynamic or private ports.
801 You can actived this three range.
802 Port number 0 is reserved and can't be used.
803 see: http://en.wikipedia.org/wiki/Port_numbers
805 __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
808 def __init__(self, name, doc, default=None, default_multi=None,
809 requires=None, multi=False, callback=None,
810 callback_params=None, validator=None, validator_params=None,
811 properties=None, allow_range=False, allow_zero=False,
812 allow_wellknown=True, allow_registred=True,
813 allow_private=False, only_warning=False):
814 self._allow_range = allow_range
815 self._min_value = None
816 self._max_value = None
817 ports_min = [0, 1, 1024, 49152]
818 ports_max = [0, 1023, 49151, 65535]
820 for index, allowed in enumerate([allow_zero,
824 if self._min_value is None:
826 self._min_value = ports_min[index]
829 elif allowed and is_finally:
830 raise ValueError(_('inconsistency in allowed range'))
832 self._max_value = ports_max[index]
834 if self._max_value is None:
835 raise ValueError(_('max value is empty'))
837 super(PortOption, self).__init__(name, doc, default=default,
838 default_multi=default_multi,
840 callback_params=callback_params,
844 validator_params=validator_params,
845 properties=properties,
846 only_warning=only_warning)
848 def _validate(self, value):
849 if self._allow_range and ":" in str(value):
850 value = str(value).split(':')
852 raise ValueError('range must have two values only')
853 if not value[0] < value[1]:
854 raise ValueError('first port in range must be'
855 ' smaller than the second one')
860 if not self._min_value <= int(val) <= self._max_value:
861 raise ValueError('port must be an between {0} and {1}'
862 ''.format(self._min_value, self._max_value))
865 class NetworkOption(Option):
866 "represents the choice of a network"
868 _opt_type = 'network'
870 def _validate(self, value):
873 def _second_level_validation(self, value):
875 if ip.iptype() == 'RESERVED':
876 raise ValueError(_("network shall not be in reserved class"))
879 class NetmaskOption(Option):
880 "represents the choice of a netmask"
882 _opt_type = 'netmask'
884 def _validate(self, value):
885 IP('0.0.0.0/{0}'.format(value))
887 def _cons_network_netmask(self, optname, value, value_):
888 #opts must be (netmask, network) options
889 self.__cons_netmask(optname, value, value_, False)
891 def _cons_ip_netmask(self, optname, value, value_):
892 #opts must be (netmask, ip) options
893 self.__cons_netmask(optname, value, value_, True)
895 def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
898 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
900 #if cidr == 32, ip same has network
901 if ip.prefixlen() != 32:
903 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
904 make_net=not make_net)
907 msg = _("invalid network {0} ({1}) "
908 "with netmask {2} ({3}),"
909 " this network is an IP")
912 msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
913 " this IP is a network")
917 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
919 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
921 raise ValueError(msg.format(val_ipnetwork, optname,
922 val_netmask, self._name))
925 class BroadcastOption(Option):
926 def _validate(self, value):
927 IP('{0}/32'.format(value))
930 class DomainnameOption(Option):
931 "represents the choice of a domain name"
932 __slots__ = ('_type', '_allow_ip')
933 _opt_type = 'domainname'
935 def __init__(self, name, doc, default=None, default_multi=None,
936 requires=None, multi=False, callback=None,
937 callback_params=None, validator=None, validator_params=None,
938 properties=None, allow_ip=False, type_='domainname',
940 #netbios: for MS domain
941 #hostname: to identify the device
943 #fqdn: with tld, not supported yet
944 if type_ not in ['netbios', 'hostname', 'domainname']:
945 raise ValueError(_('unknown type_ {0} for hostname').format(type_))
947 if allow_ip not in [True, False]:
948 raise ValueError(_('allow_ip must be a boolean'))
949 self._allow_ip = allow_ip
950 super(DomainnameOption, self).__init__(name, doc, default=default,
951 default_multi=default_multi,
953 callback_params=callback_params,
957 validator_params=validator_params,
958 properties=properties,
959 only_warning=only_warning)
961 def _validate(self, value):
962 if self._allow_ip is True:
964 IP('{0}/32'.format(value))
968 if self._type == 'netbios':
971 elif self._type == 'hostname':
974 elif self._type == 'domainname':
978 raise ValueError(_("invalid value for {0}, must have dot"
979 "").format(self._name))
980 if len(value) > length:
981 raise ValueError(_("invalid domainname's length for"
982 " {0} (max {1})").format(self._name, length))
984 raise ValueError(_("invalid domainname's length for {0} (min 2)"
985 "").format(self._name))
986 regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
987 if re.match(regexp, value) is None:
988 raise ValueError(_('invalid domainname'))
991 class OptionDescription(BaseOption):
992 """Config's schema (organisation, group) and container of Options
993 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
995 __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
996 '_state_group_type', '_properties', '_children',
997 '_consistencies', '_calc_properties', '__weakref__',
998 '_readonly', '_impl_informations', '_state_requires',
999 '_state_consistencies', '_stated', '_state_readonly')
1000 _opt_type = 'optiondescription'
1002 def __init__(self, name, doc, children, requires=None, properties=None):
1004 :param children: a list of options (including optiondescriptions)
1007 super(OptionDescription, self).__init__(name, doc, requires, properties)
1008 child_names = [child._name for child in children]
1009 #better performance like this
1010 valid_child = copy(child_names)
1013 for child in valid_child:
1015 raise ConflictError(_('duplicate option name: '
1016 '{0}').format(child))
1018 self._children = (tuple(child_names), tuple(children))
1019 self._cache_paths = None
1020 # the group_type is useful for filtering OptionDescriptions in a config
1021 self._group_type = groups.default
1023 def impl_getdoc(self):
1024 return self.impl_get_information('doc')
1026 def __getattr__(self, name):
1027 if name in self.__slots__:
1028 return object.__getattribute__(self, name)
1030 return self._children[1][self._children[0].index(name)]
1032 raise AttributeError(_('unknown Option {0} '
1033 'in OptionDescription {1}'
1034 '').format(name, self._name))
1036 def impl_getkey(self, config):
1037 return tuple([child.impl_getkey(getattr(config, child._name))
1038 for child in self.impl_getchildren()])
1040 def impl_getpaths(self, include_groups=False, _currpath=None):
1041 """returns a list of all paths in self, recursively
1042 _currpath should not be provided (helps with recursion)
1044 if _currpath is None:
1047 for option in self.impl_getchildren():
1049 if isinstance(option, OptionDescription):
1051 paths.append('.'.join(_currpath + [attr]))
1052 paths += option.impl_getpaths(include_groups=include_groups,
1053 _currpath=_currpath + [attr])
1055 paths.append('.'.join(_currpath + [attr]))
1058 def impl_getchildren(self):
1059 return self._children[1]
1061 def impl_build_cache(self,
1065 _consistencies=None,
1066 force_no_consistencies=False):
1067 if _currpath is None and self._cache_paths is not None:
1070 if _currpath is None:
1073 if not force_no_consistencies:
1077 if cache_path is None:
1080 for option in self.impl_getchildren():
1082 if option in cache_option:
1083 raise ConflictError(_('duplicate option: {0}').format(option))
1085 cache_option.append(option)
1086 if not force_no_consistencies:
1087 option._readonly = True
1088 cache_path.append(str('.'.join(_currpath + [attr])))
1089 if not isinstance(option, OptionDescription):
1090 if not force_no_consistencies and \
1091 option._consistencies is not None:
1092 for consistency in option._consistencies:
1093 func, opt = consistency
1094 opts = (option, opt)
1095 _consistencies.setdefault(opt,
1096 []).append((func, opts))
1097 _consistencies.setdefault(option,
1098 []).append((func, opts))
1100 _currpath.append(attr)
1101 option.impl_build_cache(cache_path,
1105 force_no_consistencies)
1108 self._cache_paths = (tuple(cache_option), tuple(cache_path))
1109 if not force_no_consistencies:
1110 self._consistencies = _consistencies
1111 self._readonly = True
1113 def impl_get_opt_by_path(self, path):
1115 return self._cache_paths[0][self._cache_paths[1].index(path)]
1117 raise AttributeError(_('no option for path {0}').format(path))
1119 def impl_get_path_by_opt(self, opt):
1121 return self._cache_paths[1][self._cache_paths[0].index(opt)]
1123 raise AttributeError(_('no option {0} found').format(opt))
1125 # ____________________________________________________________
1126 def impl_set_group_type(self, group_type):
1127 """sets a given group object to an OptionDescription
1129 :param group_type: an instance of `GroupType` or `MasterGroupType`
1130 that lives in `setting.groups`
1132 if self._group_type != groups.default:
1133 raise TypeError(_('cannot change group_type if already set '
1134 '(old {0}, new {1})').format(self._group_type,
1136 if isinstance(group_type, groups.GroupType):
1137 self._group_type = group_type
1138 if isinstance(group_type, groups.MasterGroupType):
1139 #if master (same name has group) is set
1140 identical_master_child_name = False
1141 #for collect all slaves
1144 for child in self.impl_getchildren():
1145 if isinstance(child, OptionDescription):
1146 raise ValueError(_("master group {0} shall not have "
1147 "a subgroup").format(self._name))
1148 if isinstance(child, SymLinkOption):
1149 raise ValueError(_("master group {0} shall not have "
1150 "a symlinkoption").format(self._name))
1151 if not child.impl_is_multi():
1152 raise ValueError(_("not allowed option {0} "
1154 ": this option is not a multi"
1155 "").format(child._name, self._name))
1156 if child._name == self._name:
1157 identical_master_child_name = True
1158 child._multitype = multitypes.master
1161 slaves.append(child)
1163 raise ValueError(_('master group with wrong'
1164 ' master name for {0}'
1165 ).format(self._name))
1166 master._master_slaves = tuple(slaves)
1167 for child in self.impl_getchildren():
1169 child._master_slaves = master
1170 child._multitype = multitypes.slave
1171 if not identical_master_child_name:
1172 raise ValueError(_("no child has same nom has master group"
1173 " for: {0}").format(self._name))
1175 raise ValueError(_('group_type: {0}'
1176 ' not allowed').format(group_type))
1178 def impl_get_group_type(self):
1179 return self._group_type
1181 def _valid_consistency(self, opt, value, context=None, index=None):
1182 consistencies = self._consistencies.get(opt)
1183 if consistencies is not None:
1184 for consistency in consistencies:
1185 opt_ = consistency[1]
1186 ret = opt_[0]._launch_consistency(consistency[0],
1196 def _impl_getstate(self, descr=None):
1197 """enables us to export into a dict
1198 :param descr: parent :class:`tiramisu.option.OptionDescription`
1201 self.impl_build_cache()
1203 super(OptionDescription, self)._impl_getstate(descr)
1204 self._state_group_type = str(self._group_type)
1205 for option in self.impl_getchildren():
1206 option._impl_getstate(descr)
1208 def __getstate__(self):
1209 """special method to enable the serialization with pickle
1213 # the `_state` attribute is a flag that which tells us if
1214 # the serialization can be performed
1216 except AttributeError:
1217 # if cannot delete, _impl_getstate never launch
1218 # launch it recursivement
1219 # _stated prevent __getstate__ launch more than one time
1220 # _stated is delete, if re-serialize, re-lauch _impl_getstate
1221 self._impl_getstate()
1223 return super(OptionDescription, self).__getstate__(stated)
1225 def _impl_setstate(self, descr=None):
1226 """enables us to import from a dict
1227 :param descr: parent :class:`tiramisu.option.OptionDescription`
1230 self._cache_paths = None
1231 self.impl_build_cache(force_no_consistencies=True)
1233 self._group_type = getattr(groups, self._state_group_type)
1234 del(self._state_group_type)
1235 super(OptionDescription, self)._impl_setstate(descr)
1236 for option in self.impl_getchildren():
1237 option._impl_setstate(descr)
1239 def __setstate__(self, state):
1240 super(OptionDescription, self).__setstate__(state)
1243 except AttributeError:
1244 self._impl_setstate()
1247 def validate_requires_arg(requires, name):
1248 """check malformed requirements
1249 and tranform dict to internal tuple
1251 :param requires: have a look at the
1252 :meth:`tiramisu.setting.Settings.apply_requires` method to
1254 the description of the requires dictionary
1256 if requires is None:
1261 # start parsing all requires given by user (has dict)
1262 # transforme it to a tuple
1263 for require in requires:
1264 if not type(require) == dict:
1265 raise ValueError(_("malformed requirements type for option:"
1266 " {0}, must be a dict").format(name))
1267 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1269 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1270 if unknown_keys != frozenset():
1271 raise ValueError('malformed requirements for option: {0}'
1272 ' unknown keys {1}, must only '
1276 # prepare all attributes
1278 option = require['option']
1279 expected = require['expected']
1280 action = require['action']
1282 raise ValueError(_("malformed requirements for option: {0}"
1283 " require must have option, expected and"
1284 " action keys").format(name))
1285 inverse = require.get('inverse', False)
1286 if inverse not in [True, False]:
1287 raise ValueError(_('malformed requirements for option: {0}'
1288 ' inverse must be boolean'))
1289 transitive = require.get('transitive', True)
1290 if transitive not in [True, False]:
1291 raise ValueError(_('malformed requirements for option: {0}'
1292 ' transitive must be boolean'))
1293 same_action = require.get('same_action', True)
1294 if same_action not in [True, False]:
1295 raise ValueError(_('malformed requirements for option: {0}'
1296 ' same_action must be boolean'))
1298 if not isinstance(option, Option):
1299 raise ValueError(_('malformed requirements '
1300 'must be an option in option {0}').format(name))
1301 if option.impl_is_multi():
1302 raise ValueError(_('malformed requirements option {0} '
1303 'should not be a multi').format(name))
1304 if expected is not None:
1306 option._validate(expected)
1307 except ValueError as err:
1308 raise ValueError(_('malformed requirements second argument '
1309 'must be valid for option {0}'
1310 ': {1}').format(name, err))
1311 if action in config_action:
1312 if inverse != config_action[action]:
1313 raise ValueError(_("inconsistency in action types"
1315 " action: {1}").format(name, action))
1317 config_action[action] = inverse
1318 if action not in ret_requires:
1319 ret_requires[action] = {}
1320 if option not in ret_requires[action]:
1321 ret_requires[action][option] = (option, [expected], action,
1322 inverse, transitive, same_action)
1324 ret_requires[action][option][1].append(expected)
1325 # transform dict to tuple
1327 for opt_requires in ret_requires.values():
1329 for require in opt_requires.values():
1330 ret_action.append((require[0], tuple(require[1]), require[2],
1331 require[3], require[4], require[5]))
1332 ret.append(tuple(ret_action))
1333 return frozenset(config_action.keys()), tuple(ret)
1336 def validate_callback(callback, callback_params, type_):
1337 if type(callback) != FunctionType:
1338 raise ValueError(_('{0} should be a function').format(type_))
1339 if callback_params is not None:
1340 if not isinstance(callback_params, dict):
1341 raise ValueError(_('{0}_params should be a dict').format(type_))
1342 for key, callbacks in callback_params.items():
1343 if key != '' and len(callbacks) != 1:
1344 raise ValueError(_('{0}_params with key {1} should not have '
1345 'length different to 1').format(type_,
1347 if not isinstance(callbacks, tuple):
1348 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1349 ).format(type_, key))
1350 for callbk in callbacks:
1351 if isinstance(callbk, tuple):
1352 option, force_permissive = callbk
1353 if type_ == 'validator' and not force_permissive:
1354 raise ValueError(_('validator not support tuple'))
1355 if not isinstance(option, Option) and not \
1356 isinstance(option, SymLinkOption):
1357 raise ValueError(_('{0}_params should have an option '
1358 'not a {0} for first argument'
1359 ).format(type_, type(option)))
1360 if force_permissive not in [True, False]:
1361 raise ValueError(_('{0}_params should have a boolean'
1362 ' not a {0} for second argument'
1363 ).format(type_, type(