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
29 from tiramisu.error import ConflictError, ConfigError
30 from tiramisu.setting import groups, multitypes
31 from tiramisu.i18n import _
32 from tiramisu.autolib import carry_out_calculation
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')
41 "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
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_'):
53 #____________________________________________________________
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
62 __slots__ = ('_name', '_requires', '_properties', '_readonly',
63 '_consistencies', '_calc_properties', '_impl_informations',
64 '_state_consistencies', '_state_readonly', '_state_requires',
67 def __init__(self, name, doc, requires, properties):
68 if not valid_name(name):
69 raise ValueError(_("invalid name: {0} for option").format(name))
71 self._impl_informations = {}
72 self.impl_set_information('doc', doc)
73 self._calc_properties, self._requires = validate_requires_arg(
75 self._consistencies = None
76 if properties is None:
78 if not isinstance(properties, tuple):
79 raise TypeError(_('invalid properties type {0} for {1},'
80 ' must be a tuple').format(
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'...
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`.
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)
100 if not name.startswith('_state') and name not in ('_cache_paths',
107 #so _name is already set
112 if self._readonly is True:
114 # already readonly and try to re set readonly
115 # don't raise, just exit
118 except AttributeError:
121 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
122 " read-only").format(
123 self.__class__.__name__,
126 object.__setattr__(self, name, value)
129 def impl_set_information(self, key, value):
130 """updates the information's attribute
131 (which is a dictionary)
133 :param key: information's key (ex: "help", "doc"
134 :param value: information's value (ex: "the help string")
136 self._impl_informations[key] = value
138 def impl_get_information(self, key, default=None):
139 """retrieves one information's item
141 :param key: the item string (ex: "help")
143 if key in self._impl_informations:
144 return self._impl_informations[key]
145 elif default is not None:
148 raise ValueError(_("information's item not found: {0}").format(
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.
157 :type descr: :class:`tiramisu.option.OptionDescription`
158 :param load: `True` if we are at the init of the option description
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)
168 consistencies = self._state_consistencies
170 consistencies = self._consistencies
171 if isinstance(consistencies, list):
173 for consistency in consistencies:
175 new_value.append((consistency[0],
176 descr.impl_get_opt_by_path(
179 new_value.append((consistency[0],
180 descr.impl_get_path_by_opt(
185 for key, _consistencies in consistencies.items():
187 for key_cons, _cons in _consistencies:
191 _list_cons.append(descr.impl_get_opt_by_path(_con))
193 _list_cons.append(descr.impl_get_path_by_opt(_con))
194 new_value[key].append((key_cons, tuple(_list_cons)))
196 del(self._state_consistencies)
197 self._consistencies = new_value
199 self._state_consistencies = new_value
201 def _impl_convert_requires(self, descr, load=False):
202 """export of the requires during the serialization process
204 :type descr: :class:`tiramisu.option.OptionDescription`
205 :param load: `True` if we are at the init of the option description
208 if not load and self._requires is None:
209 self._state_requires = None
210 elif load and self._state_requires is None:
211 self._requires = None
212 del(self._state_requires)
215 _requires = self._state_requires
217 _requires = self._requires
219 for requires in _requires:
221 for require in requires:
223 new_require = [descr.impl_get_opt_by_path(require[0])]
225 new_require = [descr.impl_get_path_by_opt(require[0])]
226 new_require.extend(require[1:])
227 new_requires.append(tuple(new_require))
228 new_value.append(tuple(new_requires))
230 del(self._state_requires)
231 self._requires = new_value
233 self._state_requires = new_value
236 def _impl_getstate(self, descr):
237 """the under the hood stuff that need to be done
238 before the serialization.
240 :param descr: the parent :class:`tiramisu.option.OptionDescription`
243 self._impl_convert_consistencies(descr)
244 self._impl_convert_requires(descr)
246 self._state_readonly = self._readonly
247 except AttributeError:
250 def __getstate__(self, stated=True):
251 """special method to enable the serialization with pickle
252 Usualy, a `__getstate__` method does'nt need any parameter,
253 but somme under the hood stuff need to be done before this action
255 :parameter stated: if stated is `True`, the serialization protocol
256 can be performed, not ready yet otherwise
257 :parameter type: bool
261 except AttributeError:
262 raise SystemError(_('cannot serialize Option, '
263 'only in OptionDescription'))
265 for subclass in self.__class__.__mro__:
266 if subclass is not object:
267 slots.update(subclass.__slots__)
268 slots -= frozenset(['_cache_paths', '__weakref__'])
271 # remove variable if save variable converted
272 # in _state_xxxx variable
273 if '_state' + slot not in slots:
274 if slot.startswith('_state'):
276 states[slot] = getattr(self, slot)
277 # remove _state_xxx variable
278 self.__delattr__(slot)
281 states[slot] = getattr(self, slot)
282 except AttributeError:
285 del(states['_stated'])
289 def _impl_setstate(self, descr):
290 """the under the hood stuff that need to be done
291 before the serialization.
293 :type descr: :class:`tiramisu.option.OptionDescription`
295 self._impl_convert_consistencies(descr, load=True)
296 self._impl_convert_requires(descr, load=True)
298 self._readonly = self._state_readonly
299 del(self._state_readonly)
301 except AttributeError:
304 def __setstate__(self, state):
305 """special method that enables us to serialize (pickle)
307 Usualy, a `__setstate__` method does'nt need any parameter,
308 but somme under the hood stuff need to be done before this action
310 :parameter state: a dict is passed to the loads, it is the attributes
311 of the options object
314 for key, value in state.items():
315 setattr(self, key, value)
318 class Option(BaseOption):
320 Abstract base class for configuration option's.
322 Reminder: an Option object is **not** a container for the value.
324 __slots__ = ('_multi', '_validator', '_default_multi', '_default',
325 '_callback', '_multitype', '_master_slaves', '__weakref__')
328 def __init__(self, name, doc, default=None, default_multi=None,
329 requires=None, multi=False, callback=None,
330 callback_params=None, validator=None, validator_params=None,
333 :param name: the option's name
334 :param doc: the option's description
335 :param default: specifies the default value of the option,
336 for a multi : ['bla', 'bla', 'bla']
337 :param default_multi: 'bla' (used in case of a reset to default only at
339 :param requires: is a list of names of options located anywhere
340 in the configuration.
341 :param multi: if true, the option's value is a list
342 :param callback: the name of a function. If set, the function's output
343 is responsible of the option's value
344 :param callback_params: the callback's parameter
345 :param validator: the name of a function which stands for a custom
346 validation of the value
347 :param validator_params: the validator's parameters
348 :param properties: tuple of default properties
351 super(Option, self).__init__(name, doc, requires, properties)
353 if validator is not None:
354 validate_callback(validator, validator_params, 'validator')
355 self._validator = (validator, validator_params)
357 self._validator = None
358 if not self._multi and default_multi is not None:
359 raise ValueError(_("a default_multi is set whereas multi is False"
360 " in option: {0}").format(name))
361 if default_multi is not None:
363 self._validate(default_multi)
364 except ValueError as err:
365 raise ValueError(_("invalid default_multi value {0} "
366 "for option {1}: {2}").format(
367 str(default_multi), name, err))
368 if callback is not None and (default is not None or
369 default_multi is not None):
370 raise ValueError(_("default value not allowed if option: {0} "
371 "is calculated").format(name))
372 if callback is None and callback_params is not None:
373 raise ValueError(_("params defined for a callback function but "
374 "no callback defined"
375 " yet for option {0}").format(name))
376 if callback is not None:
377 validate_callback(callback, callback_params, 'callback')
378 self._callback = (callback, callback_params)
380 self._callback = None
384 self._multitype = multitypes.default
385 self._default_multi = default_multi
386 self.impl_validate(default)
387 self._default = default
389 def _launch_consistency(self, func, opt, vals, context, index, opt_):
390 if context is not None:
391 descr = context.cfgimpl_get_description()
393 #values are for self, search opt_ values
395 if context is not None:
396 path = descr.impl_get_path_by_opt(opt_)
397 values_ = context._getattr(path, validate=False)
399 values_ = opt_.impl_getdefault()
400 if index is not None:
401 #value is not already set, could be higher
403 values_ = values_[index]
407 #values are for opt_, search self values
409 if context is not None:
410 path = descr.impl_get_path_by_opt(self)
411 values = context._getattr(path, validate=False)
413 values = self.impl_getdefault()
414 if index is not None:
415 #value is not already set, could be higher
417 values = values[index]
420 if index is None and self.impl_is_multi():
421 for index in range(0, len(values)):
423 value = values[index]
424 value_ = values_[index]
428 if None not in (value, value_):
429 getattr(self, func)(opt_._name, value, value_)
431 if None not in (values, values_):
432 getattr(self, func)(opt_._name, values, values_)
434 def impl_validate(self, value, context=None, validate=True):
436 :param value: the option's value
437 :param validate: if true enables ``self._validator`` validation
442 def val_validator(val):
443 if self._validator is not None:
444 if self._validator[1] is not None:
445 validator_params = deepcopy(self._validator[1])
446 if '' in validator_params:
447 lst = list(validator_params[''])
449 validator_params[''] = tuple(lst)
451 validator_params[''] = (val,)
453 validator_params = {'': (val,)}
454 ret = carry_out_calculation(self._name, config=context,
455 callback=self._validator[0],
456 callback_params=validator_params)
457 if ret not in [False, True]:
458 raise ConfigError(_('validator should return a boolean, '
459 'not {0}').format(ret))
464 def do_validation(_value, _index=None):
467 if not val_validator(_value):
468 raise ValueError(_("invalid value {0} "
469 "for option {1} for object {2}"
472 self.__class__.__name__))
474 self._validate(_value)
475 except ValueError as err:
476 raise ValueError(_("invalid value {0} for option {1}: {2}"
477 "").format(_value, self._name, err))
478 if context is not None:
479 descr._valid_consistency(self, _value, context, _index)
481 # generic calculation
482 if context is not None:
483 descr = context.cfgimpl_get_description()
487 if not isinstance(value, list):
488 raise ValueError(_("invalid value {0} for option {1} "
489 "which must be a list").format(value,
491 for index in range(0, len(value)):
493 do_validation(val, index)
495 def impl_getdefault(self, default_multi=False):
496 "accessing the default value"
497 if not default_multi or not self.impl_is_multi():
500 return self.getdefault_multi()
502 def impl_getdefault_multi(self):
503 "accessing the default value for a multi"
504 return self._default_multi
506 def impl_get_multitype(self):
507 return self._multitype
509 def impl_get_master_slaves(self):
510 return self._master_slaves
512 def impl_is_empty_by_default(self):
513 "no default value has been set yet"
514 if ((not self.impl_is_multi() and self._default is None) or
515 (self.impl_is_multi() and (self._default == []
516 or None in self._default))):
520 def impl_getdoc(self):
521 "accesses the Option's doc"
522 return self.impl_get_information('doc')
524 def impl_has_callback(self):
525 "to know if a callback has been defined or not"
526 if self._callback is None:
531 def impl_getkey(self, value):
534 def impl_is_multi(self):
537 def impl_add_consistency(self, func, opt):
538 if self._consistencies is None:
539 self._consistencies = []
540 if not isinstance(opt, Option):
541 raise ValueError('consistency must be set with an option')
543 raise ValueError('cannot add consistency with itself')
544 if self.impl_is_multi() != opt.impl_is_multi():
545 raise ValueError('options in consistency'
546 ' should be multi in two sides')
547 func = '_cons_{0}'.format(func)
548 self._launch_consistency(func,
550 self.impl_getdefault(),
552 self._consistencies.append((func, opt))
553 self.impl_validate(self.impl_getdefault())
555 def _cons_not_equal(self, optname, value, value_):
557 raise ValueError(_("invalid value {0} for option {1} "
558 "must be different as {2} option"
559 "").format(value, self._name, optname))
562 class ChoiceOption(Option):
563 """represents a choice out of several objects.
565 The option can also have the value ``None``
568 __slots__ = ('_values', '_open_values')
571 def __init__(self, name, doc, values, default=None, default_multi=None,
572 requires=None, multi=False, callback=None,
573 callback_params=None, open_values=False, validator=None,
574 validator_params=None, properties=()):
576 :param values: is a list of values the option can possibly take
578 if not isinstance(values, tuple):
579 raise TypeError(_('values must be a tuple for {0}').format(name))
580 self._values = values
581 if open_values not in (True, False):
582 raise TypeError(_('open_values must be a boolean for '
584 self._open_values = open_values
585 super(ChoiceOption, self).__init__(name, doc, default=default,
586 default_multi=default_multi,
588 callback_params=callback_params,
592 validator_params=validator_params,
593 properties=properties)
595 def impl_get_values(self):
598 def impl_is_openvalues(self):
599 return self._open_values
601 def _validate(self, value):
602 if not self._open_values and not value in self._values:
603 raise ValueError(_('value {0} is not permitted, '
604 'only {1} is allowed'
605 '').format(value, self._values))
608 class BoolOption(Option):
609 "represents a choice between ``True`` and ``False``"
613 def _validate(self, value):
614 if not isinstance(value, bool):
615 raise ValueError(_('value must be a boolean'))
618 class IntOption(Option):
619 "represents a choice of an integer"
623 def _validate(self, value):
624 if not isinstance(value, int):
625 raise ValueError(_('value must be an integer'))
628 class FloatOption(Option):
629 "represents a choice of a floating point number"
633 def _validate(self, value):
634 if not isinstance(value, float):
635 raise ValueError(_('value must be a float'))
638 class StrOption(Option):
639 "represents the choice of a string"
643 def _validate(self, value):
644 if not isinstance(value, str):
645 raise ValueError(_('value must be a string, not '
646 '{0}').format(type(value)))
649 if sys.version_info[0] >= 3:
650 #UnicodeOption is same has StrOption in python 3+
651 class UnicodeOption(StrOption):
655 class UnicodeOption(Option):
656 "represents the choice of a unicode string"
658 _opt_type = 'unicode'
661 def _validate(self, value):
662 if not isinstance(value, unicode):
663 raise ValueError(_('value must be an unicode'))
666 class SymLinkOption(BaseOption):
667 __slots__ = ('_name', '_opt', '_state_opt')
668 _opt_type = 'symlink'
669 #not return _opt consistencies
672 def __init__(self, name, opt):
674 if not isinstance(opt, Option):
675 raise ValueError(_('malformed symlinkoption '
677 'for symlink {0}').format(name))
679 self._readonly = True
681 def __getattr__(self, name):
682 if name in ('_name', '_opt', '_opt_type', '_readonly'):
683 return object.__getattr__(self, name)
685 return getattr(self._opt, name)
687 def _impl_getstate(self, descr):
688 super(SymLinkOption, self)._impl_getstate(descr)
689 self._state_opt = descr.impl_get_path_by_opt(self._opt)
691 def _impl_setstate(self, descr):
692 self._opt = descr.impl_get_opt_by_path(self._state_opt)
694 super(SymLinkOption, self)._impl_setstate(descr)
696 def _impl_convert_consistencies(self, descr, load=False):
698 del(self._state_consistencies)
700 self._state_consistencies = None
703 class IPOption(Option):
704 "represents the choice of an ip"
705 __slots__ = ('_only_private', '_allow_reserved')
708 def __init__(self, name, doc, default=None, default_multi=None,
709 requires=None, multi=False, callback=None,
710 callback_params=None, validator=None, validator_params=None,
711 properties=None, only_private=False, allow_reserved=False):
712 self._only_private = only_private
713 self._allow_reserved = allow_reserved
714 super(IPOption, self).__init__(name, doc, default=default,
715 default_multi=default_multi,
717 callback_params=callback_params,
721 validator_params=validator_params,
722 properties=properties)
724 def _validate(self, value):
725 ip = IP('{0}/32'.format(value))
726 if not self._allow_reserved and ip.iptype() == 'RESERVED':
727 raise ValueError(_("IP mustn't not be in reserved class"))
728 if self._only_private and not ip.iptype() == 'PRIVATE':
729 raise ValueError(_("IP must be in private class"))
732 class PortOption(Option):
733 """represents the choice of a port
734 The port numbers are divided into three ranges:
735 the well-known ports,
736 the registered ports,
737 and the dynamic or private ports.
738 You can actived this three range.
739 Port number 0 is reserved and can't be used.
740 see: http://en.wikipedia.org/wiki/Port_numbers
742 __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
745 def __init__(self, name, doc, default=None, default_multi=None,
746 requires=None, multi=False, callback=None,
747 callback_params=None, validator=None, validator_params=None,
748 properties=None, allow_range=False, allow_zero=False,
749 allow_wellknown=True, allow_registred=True,
750 allow_private=False):
751 self._allow_range = allow_range
752 self._min_value = None
753 self._max_value = None
754 ports_min = [0, 1, 1024, 49152]
755 ports_max = [0, 1023, 49151, 65535]
757 for index, allowed in enumerate([allow_zero,
761 if self._min_value is None:
763 self._min_value = ports_min[index]
766 elif allowed and is_finally:
767 raise ValueError(_('inconsistency in allowed range'))
769 self._max_value = ports_max[index]
771 if self._max_value is None:
772 raise ValueError(_('max value is empty'))
774 super(PortOption, self).__init__(name, doc, default=default,
775 default_multi=default_multi,
777 callback_params=callback_params,
781 validator_params=validator_params,
782 properties=properties)
784 def _validate(self, value):
785 if self._allow_range and ":" in str(value):
786 value = str(value).split(':')
788 raise ValueError('range must have two values only')
789 if not value[0] < value[1]:
790 raise ValueError('first port in range must be'
791 ' smaller than the second one')
796 if not self._min_value <= int(val) <= self._max_value:
797 raise ValueError('port must be an between {0} and {1}'
798 ''.format(self._min_value, self._max_value))
801 class NetworkOption(Option):
802 "represents the choice of a network"
804 _opt_type = 'network'
806 def _validate(self, value):
808 if ip.iptype() == 'RESERVED':
809 raise ValueError(_("network shall not be in reserved class"))
812 class NetmaskOption(Option):
813 "represents the choice of a netmask"
815 _opt_type = 'netmask'
817 def _validate(self, value):
818 IP('0.0.0.0/{0}'.format(value))
820 def _cons_network_netmask(self, optname, value, value_):
821 #opts must be (netmask, network) options
822 self.__cons_netmask(optname, value, value_, False)
824 def _cons_ip_netmask(self, optname, value, value_):
825 #opts must be (netmask, ip) options
826 self.__cons_netmask(optname, value, value_, True)
828 #def __cons_netmask(self, opt, value, context, index, opts, make_net):
829 def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
832 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
834 #if cidr == 32, ip same has network
835 if ip.prefixlen() != 32:
837 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
838 make_net=not make_net)
841 msg = _("invalid network {0} ({1}) "
842 "with netmask {2} ({3}),"
843 " this network is an IP")
846 msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
847 " this IP is a network")
851 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
853 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
855 raise ValueError(msg.format(val_ipnetwork, optname,
856 val_netmask, self._name))
859 class DomainnameOption(Option):
860 "represents the choice of a domain name"
861 __slots__ = ('_type', '_allow_ip')
862 _opt_type = 'domainname'
864 def __init__(self, name, doc, default=None, default_multi=None,
865 requires=None, multi=False, callback=None,
866 callback_params=None, validator=None, validator_params=None,
867 properties=None, allow_ip=False, type_='domainname'):
868 #netbios: for MS domain
869 #hostname: to identify the device
871 #fqdn: with tld, not supported yet
872 if type_ not in ['netbios', 'hostname', 'domainname']:
873 raise ValueError(_('unknown type_ {0} for hostname').format(type_))
875 if allow_ip not in [True, False]:
876 raise ValueError(_('allow_ip must be a boolean'))
877 self._allow_ip = allow_ip
878 super(DomainnameOption, self).__init__(name, doc, default=default,
879 default_multi=default_multi,
881 callback_params=callback_params,
885 validator_params=validator_params,
886 properties=properties)
888 def _validate(self, value):
889 if self._allow_ip is True:
891 IP('{0}/32'.format(value))
895 if self._type == 'netbios':
898 elif self._type == 'hostname':
901 elif self._type == 'domainname':
905 raise ValueError(_("invalid value for {0}, must have dot"
906 "").format(self._name))
907 if len(value) > length:
908 raise ValueError(_("invalid domainname's length for"
909 " {0} (max {1})").format(self._name, length))
911 raise ValueError(_("invalid domainname's length for {0} (min 2)"
912 "").format(self._name))
913 regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
914 if re.match(regexp, value) is None:
915 raise ValueError(_('invalid domainname'))
918 class OptionDescription(BaseOption):
919 """Config's schema (organisation, group) and container of Options
920 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
922 __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
923 '_state_group_type', '_properties', '_children',
924 '_consistencies', '_calc_properties', '__weakref__',
925 '_readonly', '_impl_informations', '_state_requires',
926 '_state_consistencies', '_stated', '_state_readonly')
927 _opt_type = 'optiondescription'
929 def __init__(self, name, doc, children, requires=None, properties=None):
931 :param children: a list of options (including optiondescriptions)
934 super(OptionDescription, self).__init__(name, doc, requires, properties)
935 child_names = [child._name for child in children]
936 #better performance like this
937 valid_child = copy(child_names)
940 for child in valid_child:
942 raise ConflictError(_('duplicate option name: '
943 '{0}').format(child))
945 self._children = (tuple(child_names), tuple(children))
946 self._cache_paths = None
947 # the group_type is useful for filtering OptionDescriptions in a config
948 self._group_type = groups.default
950 def impl_getdoc(self):
951 return self.impl_get_information('doc')
953 def __getattr__(self, name):
954 if name in self.__slots__:
955 return object.__getattribute__(self, name)
957 return self._children[1][self._children[0].index(name)]
959 raise AttributeError(_('unknown Option {0} '
960 'in OptionDescription {1}'
961 '').format(name, self._name))
963 def impl_getkey(self, config):
964 return tuple([child.impl_getkey(getattr(config, child._name))
965 for child in self.impl_getchildren()])
967 def impl_getpaths(self, include_groups=False, _currpath=None):
968 """returns a list of all paths in self, recursively
969 _currpath should not be provided (helps with recursion)
971 if _currpath is None:
974 for option in self.impl_getchildren():
976 if isinstance(option, OptionDescription):
978 paths.append('.'.join(_currpath + [attr]))
979 paths += option.impl_getpaths(include_groups=include_groups,
980 _currpath=_currpath + [attr])
982 paths.append('.'.join(_currpath + [attr]))
985 def impl_getchildren(self):
986 return self._children[1]
988 def impl_build_cache(self,
993 force_no_consistencies=False):
994 if _currpath is None and self._cache_paths is not None:
997 if _currpath is None:
1000 if not force_no_consistencies:
1004 if cache_path is None:
1007 for option in self.impl_getchildren():
1009 if option in cache_option:
1010 raise ConflictError(_('duplicate option: {0}').format(option))
1012 cache_option.append(option)
1013 if not force_no_consistencies:
1014 option._readonly = True
1015 cache_path.append(str('.'.join(_currpath + [attr])))
1016 if not isinstance(option, OptionDescription):
1017 if not force_no_consistencies and \
1018 option._consistencies is not None:
1019 for consistency in option._consistencies:
1020 func, opt = consistency
1021 opts = (option, opt)
1022 _consistencies.setdefault(opt,
1023 []).append((func, opts))
1024 _consistencies.setdefault(option,
1025 []).append((func, opts))
1027 _currpath.append(attr)
1028 option.impl_build_cache(cache_path,
1032 force_no_consistencies)
1035 self._cache_paths = (tuple(cache_option), tuple(cache_path))
1036 if not force_no_consistencies:
1037 self._consistencies = _consistencies
1038 self._readonly = True
1040 def impl_get_opt_by_path(self, path):
1042 return self._cache_paths[0][self._cache_paths[1].index(path)]
1044 raise AttributeError(_('no option for path {0}').format(path))
1046 def impl_get_path_by_opt(self, opt):
1048 return self._cache_paths[1][self._cache_paths[0].index(opt)]
1050 raise AttributeError(_('no option {0} found').format(opt))
1052 # ____________________________________________________________
1053 def impl_set_group_type(self, group_type):
1054 """sets a given group object to an OptionDescription
1056 :param group_type: an instance of `GroupType` or `MasterGroupType`
1057 that lives in `setting.groups`
1059 if self._group_type != groups.default:
1060 raise TypeError(_('cannot change group_type if already set '
1061 '(old {0}, new {1})').format(self._group_type,
1063 if isinstance(group_type, groups.GroupType):
1064 self._group_type = group_type
1065 if isinstance(group_type, groups.MasterGroupType):
1066 #if master (same name has group) is set
1067 identical_master_child_name = False
1068 #for collect all slaves
1071 for child in self.impl_getchildren():
1072 if isinstance(child, OptionDescription):
1073 raise ValueError(_("master group {0} shall not have "
1074 "a subgroup").format(self._name))
1075 if isinstance(child, SymLinkOption):
1076 raise ValueError(_("master group {0} shall not have "
1077 "a symlinkoption").format(self._name))
1078 if not child.impl_is_multi():
1079 raise ValueError(_("not allowed option {0} "
1081 ": this option is not a multi"
1082 "").format(child._name, self._name))
1083 if child._name == self._name:
1084 identical_master_child_name = True
1085 child._multitype = multitypes.master
1088 slaves.append(child)
1090 raise ValueError(_('master group with wrong'
1091 ' master name for {0}'
1092 ).format(self._name))
1093 master._master_slaves = tuple(slaves)
1094 for child in self.impl_getchildren():
1096 child._master_slaves = master
1097 child._multitype = multitypes.slave
1098 if not identical_master_child_name:
1099 raise ValueError(_("no child has same nom has master group"
1100 " for: {0}").format(self._name))
1102 raise ValueError(_('group_type: {0}'
1103 ' not allowed').format(group_type))
1105 def impl_get_group_type(self):
1106 return self._group_type
1108 def _valid_consistency(self, opt, value, context=None, index=None):
1109 consistencies = self._consistencies.get(opt)
1110 if consistencies is not None:
1111 for consistency in consistencies:
1112 opt_ = consistency[1]
1113 ret = opt_[0]._launch_consistency(consistency[0],
1123 def _impl_getstate(self, descr=None):
1124 """enables us to export into a dict
1125 :param descr: parent :class:`tiramisu.option.OptionDescription`
1128 self.impl_build_cache()
1130 super(OptionDescription, self)._impl_getstate(descr)
1131 self._state_group_type = str(self._group_type)
1132 for option in self.impl_getchildren():
1133 option._impl_getstate(descr)
1135 def __getstate__(self):
1136 """special method to enable the serialization with pickle
1140 # the `_state` attribute is a flag that which tells us if
1141 # the serialization can be performed
1143 except AttributeError:
1144 # if cannot delete, _impl_getstate never launch
1145 # launch it recursivement
1146 # _stated prevent __getstate__ launch more than one time
1147 # _stated is delete, if re-serialize, re-lauch _impl_getstate
1148 self._impl_getstate()
1150 return super(OptionDescription, self).__getstate__(stated)
1152 def _impl_setstate(self, descr=None):
1153 """enables us to import from a dict
1154 :param descr: parent :class:`tiramisu.option.OptionDescription`
1157 self._cache_paths = None
1158 self.impl_build_cache(force_no_consistencies=True)
1160 self._group_type = getattr(groups, self._state_group_type)
1161 del(self._state_group_type)
1162 super(OptionDescription, self)._impl_setstate(descr)
1163 for option in self.impl_getchildren():
1164 option._impl_setstate(descr)
1166 def __setstate__(self, state):
1167 super(OptionDescription, self).__setstate__(state)
1170 except AttributeError:
1171 self._impl_setstate()
1174 def validate_requires_arg(requires, name):
1175 """check malformed requirements
1176 and tranform dict to internal tuple
1178 :param requires: have a look at the
1179 :meth:`tiramisu.setting.Settings.apply_requires` method to
1181 the description of the requires dictionary
1183 if requires is None:
1188 # start parsing all requires given by user (has dict)
1189 # transforme it to a tuple
1190 for require in requires:
1191 if not type(require) == dict:
1192 raise ValueError(_("malformed requirements type for option:"
1193 " {0}, must be a dict").format(name))
1194 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1196 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1197 if unknown_keys != frozenset():
1198 raise ValueError('malformed requirements for option: {0}'
1199 ' unknown keys {1}, must only '
1203 # prepare all attributes
1205 option = require['option']
1206 expected = require['expected']
1207 action = require['action']
1209 raise ValueError(_("malformed requirements for option: {0}"
1210 " require must have option, expected and"
1211 " action keys").format(name))
1212 inverse = require.get('inverse', False)
1213 if inverse not in [True, False]:
1214 raise ValueError(_('malformed requirements for option: {0}'
1215 ' inverse must be boolean'))
1216 transitive = require.get('transitive', True)
1217 if transitive not in [True, False]:
1218 raise ValueError(_('malformed requirements for option: {0}'
1219 ' transitive must be boolean'))
1220 same_action = require.get('same_action', True)
1221 if same_action not in [True, False]:
1222 raise ValueError(_('malformed requirements for option: {0}'
1223 ' same_action must be boolean'))
1225 if not isinstance(option, Option):
1226 raise ValueError(_('malformed requirements '
1227 'must be an option in option {0}').format(name))
1228 if option.impl_is_multi():
1229 raise ValueError(_('malformed requirements option {0} '
1230 'should not be a multi').format(name))
1231 if expected is not None:
1233 option._validate(expected)
1234 except ValueError as err:
1235 raise ValueError(_('malformed requirements second argument '
1236 'must be valid for option {0}'
1237 ': {1}').format(name, err))
1238 if action in config_action:
1239 if inverse != config_action[action]:
1240 raise ValueError(_("inconsistency in action types"
1242 " action: {1}").format(name, action))
1244 config_action[action] = inverse
1245 if action not in ret_requires:
1246 ret_requires[action] = {}
1247 if option not in ret_requires[action]:
1248 ret_requires[action][option] = (option, [expected], action,
1249 inverse, transitive, same_action)
1251 ret_requires[action][option][1].append(expected)
1252 # transform dict to tuple
1254 for opt_requires in ret_requires.values():
1256 for require in opt_requires.values():
1257 ret_action.append((require[0], tuple(require[1]), require[2],
1258 require[3], require[4], require[5]))
1259 ret.append(tuple(ret_action))
1260 return frozenset(config_action.keys()), tuple(ret)
1263 def validate_callback(callback, callback_params, type_):
1264 if type(callback) != FunctionType:
1265 raise ValueError(_('{0} should be a function').format(type_))
1266 if callback_params is not None:
1267 if not isinstance(callback_params, dict):
1268 raise ValueError(_('{0}_params should be a dict').format(type_))
1269 for key, callbacks in callback_params.items():
1270 if key != '' and len(callbacks) != 1:
1271 raise ValueError(_('{0}_params with key {1} should not have '
1272 'length different to 1').format(type_,
1274 if not isinstance(callbacks, tuple):
1275 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1276 ).format(type_, key))
1277 for callbk in callbacks:
1278 if isinstance(callbk, tuple):
1279 option, force_permissive = callbk
1280 if type_ == 'validator' and not force_permissive:
1281 raise ValueError(_('validator not support tuple'))
1282 if not isinstance(option, Option) and not \
1283 isinstance(option, SymLinkOption):
1284 raise ValueError(_('{0}_params should have an option '
1285 'not a {0} for first argument'
1286 ).format(type_, type(option)))
1287 if force_permissive not in [True, False]:
1288 raise ValueError(_('{0}_params should have a boolean'
1289 'not a {0} for second argument'
1290 ).format(type_, type(force_permissive)))