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
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 \
101 name not in ('_cache_paths', '_consistencies'):
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_args=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 wich stands for a custom
346 validation of the value
347 :param validator_args: the validator's parameters
350 super(Option, self).__init__(name, doc, requires, properties)
352 if validator is not None:
353 if type(validator) != FunctionType:
354 raise TypeError(_("validator must be a function"))
355 if validator_args is None:
357 self._validator = (validator, validator_args)
359 self._validator = None
360 if not self._multi and default_multi is not None:
361 raise ValueError(_("a default_multi is set whereas multi is False"
362 " in option: {0}").format(name))
363 if default_multi is not None:
365 self._validate(default_multi)
366 except ValueError as err:
367 raise ValueError(_("invalid default_multi value {0} "
368 "for option {1}: {2}").format(
369 str(default_multi), name, err))
370 if callback is not None and (default is not None or
371 default_multi is not None):
372 raise ValueError(_("default value not allowed if option: {0} "
373 "is calculated").format(name))
374 if callback is None and callback_params is not None:
375 raise ValueError(_("params defined for a callback function but "
376 "no callback defined"
377 " yet for option {0}").format(name))
378 if callback is not None:
379 if type(callback) != FunctionType:
380 raise ValueError('callback must be a function')
381 if callback_params is not None and \
382 not isinstance(callback_params, dict):
383 raise ValueError('callback_params must be a dict')
384 self._callback = (callback, callback_params)
386 self._callback = None
390 self._multitype = multitypes.default
391 self._default_multi = default_multi
392 self.impl_validate(default)
393 self._default = default
395 def _launch_consistency(self, func, opt, vals, context, index, opt_):
396 if context is not None:
397 descr = context.cfgimpl_get_description()
399 #values are for self, search opt_ values
401 if context is not None:
402 path = descr.impl_get_path_by_opt(opt_)
403 values_ = context._getattr(path, validate=False)
405 values_ = opt_.impl_getdefault()
406 if index is not None:
407 #value is not already set, could be higher
409 values_ = values_[index]
413 #values are for opt_, search self values
415 if context is not None:
416 path = descr.impl_get_path_by_opt(self)
417 values = context._getattr(path, validate=False)
419 values = self.impl_getdefault()
420 if index is not None:
421 #value is not already set, could be higher
423 values = values[index]
426 if index is None and self.impl_is_multi():
427 for index in range(0, len(values)):
429 value = values[index]
430 value_ = values_[index]
434 if None not in (value, value_):
435 getattr(self, func)(opt_._name, value, value_)
437 if None not in (values, values_):
438 getattr(self, func)(opt_._name, values, values_)
440 def impl_validate(self, value, context=None, validate=True):
442 :param value: the option's value
443 :param validate: if true enables ``self._validator`` validation
448 def val_validator(val):
449 if self._validator is not None:
450 callback_params = deepcopy(self._validator[1])
451 callback_params.setdefault('', []).insert(0, val)
452 return carry_out_calculation(self._name, config=context,
453 callback=self._validator[0],
454 callback_params=callback_params)
458 def do_validation(_value, _index=None):
461 if not val_validator(_value):
462 raise ValueError(_("invalid value {0} "
463 "for option {1} for object {2}"
466 self.__class__.__name__))
468 self._validate(_value)
469 except ValueError as err:
470 raise ValueError(_("invalid value {0} for option {1}: {2}"
471 "").format(_value, self._name, err))
472 if context is not None:
473 descr._valid_consistency(self, _value, context, _index)
475 # generic calculation
476 if context is not None:
477 descr = context.cfgimpl_get_description()
481 if not isinstance(value, list):
482 raise ValueError(_("invalid value {0} for option {1} "
483 "which must be a list").format(value,
485 for index in range(0, len(value)):
487 do_validation(val, index)
489 def impl_getdefault(self, default_multi=False):
490 "accessing the default value"
491 if not default_multi or not self.impl_is_multi():
494 return self.getdefault_multi()
496 def impl_getdefault_multi(self):
497 "accessing the default value for a multi"
498 return self._default_multi
500 def impl_get_multitype(self):
501 return self._multitype
503 def impl_get_master_slaves(self):
504 return self._master_slaves
506 def impl_is_empty_by_default(self):
507 "no default value has been set yet"
508 if ((not self.impl_is_multi() and self._default is None) or
509 (self.impl_is_multi() and (self._default == []
510 or None in self._default))):
514 def impl_getdoc(self):
515 "accesses the Option's doc"
516 return self.impl_get_information('doc')
518 def impl_has_callback(self):
519 "to know if a callback has been defined or not"
520 if self._callback is None:
525 def impl_getkey(self, value):
528 def impl_is_multi(self):
531 def impl_add_consistency(self, func, opt):
532 if self._consistencies is None:
533 self._consistencies = []
534 if not isinstance(opt, Option):
535 raise ValueError('consistency must be set with an option')
537 raise ValueError('cannot add consistency with itself')
538 if self.impl_is_multi() != opt.impl_is_multi():
539 raise ValueError('options in consistency'
540 ' should be multi in two sides')
541 func = '_cons_{0}'.format(func)
542 self._launch_consistency(func,
544 self.impl_getdefault(),
546 self._consistencies.append((func, opt))
547 self.impl_validate(self.impl_getdefault())
549 def _cons_not_equal(self, optname, value, value_):
551 raise ValueError(_("invalid value {0} for option {1} "
552 "must be different as {2} option"
553 "").format(value, self._name, optname))
556 class ChoiceOption(Option):
557 """represents a choice out of several objects.
559 The option can also have the value ``None``
562 __slots__ = ('_values', '_open_values')
565 def __init__(self, name, doc, values, default=None, default_multi=None,
566 requires=None, multi=False, callback=None,
567 callback_params=None, open_values=False, validator=None,
568 validator_args=None, properties=()):
570 :param values: is a list of values the option can possibly take
572 if not isinstance(values, tuple):
573 raise TypeError(_('values must be a tuple for {0}').format(name))
574 self._values = values
575 if open_values not in (True, False):
576 raise TypeError(_('open_values must be a boolean for '
578 self._open_values = open_values
579 super(ChoiceOption, self).__init__(name, doc, default=default,
580 default_multi=default_multi,
582 callback_params=callback_params,
586 validator_args=validator_args,
587 properties=properties)
589 def impl_get_values(self):
592 def impl_is_openvalues(self):
593 return self._open_values
595 def _validate(self, value):
596 if not self._open_values and not value in self._values:
597 raise ValueError(_('value {0} is not permitted, '
598 'only {1} is allowed'
599 '').format(value, self._values))
602 class BoolOption(Option):
603 "represents a choice between ``True`` and ``False``"
607 def _validate(self, value):
608 if not isinstance(value, bool):
609 raise ValueError(_('value must be a boolean'))
612 class IntOption(Option):
613 "represents a choice of an integer"
617 def _validate(self, value):
618 if not isinstance(value, int):
619 raise ValueError(_('value must be an integer'))
622 class FloatOption(Option):
623 "represents a choice of a floating point number"
627 def _validate(self, value):
628 if not isinstance(value, float):
629 raise ValueError(_('value must be a float'))
632 class StrOption(Option):
633 "represents the choice of a string"
637 def _validate(self, value):
638 if not isinstance(value, str):
639 raise ValueError(_('value must be a string, not '
640 '{0}').format(type(value)))
643 if sys.version_info[0] >= 3:
644 #UnicodeOption is same has StrOption in python 3+
645 class UnicodeOption(StrOption):
649 class UnicodeOption(Option):
650 "represents the choice of a unicode string"
652 _opt_type = 'unicode'
655 def _validate(self, value):
656 if not isinstance(value, unicode):
657 raise ValueError(_('value must be an unicode'))
660 class SymLinkOption(BaseOption):
661 __slots__ = ('_name', '_opt', '_state_opt', '_consistencies')
662 _opt_type = 'symlink'
663 #not return _opt consistencies
666 def __init__(self, name, opt):
668 if not isinstance(opt, Option):
669 raise ValueError(_('malformed symlinkoption '
671 'for symlink {0}').format(name))
673 self._readonly = True
675 def __getattr__(self, name):
676 if name in ('_name', '_opt', '_opt_type', '_readonly'):
677 return object.__getattr__(self, name)
679 return getattr(self._opt, name)
681 def _impl_getstate(self, descr):
682 super(SymLinkOption, self)._impl_getstate(descr)
683 self._state_opt = descr.impl_get_path_by_opt(self._opt)
685 def _impl_setstate(self, descr):
686 self._opt = descr.impl_get_opt_by_path(self._state_opt)
688 super(SymLinkOption, self)._impl_setstate(descr)
690 def _impl_convert_consistencies(self, descr, load=False):
692 del(self._state_consistencies)
694 self._state_consistencies = None
697 class IPOption(Option):
698 "represents the choice of an ip"
699 __slots__ = ('_only_private',)
702 def __init__(self, name, doc, default=None, default_multi=None,
703 requires=None, multi=False, callback=None,
704 callback_params=None, validator=None, validator_args=None,
705 properties=None, only_private=False):
706 self._only_private = only_private
707 super(IPOption, self).__init__(name, doc, default=default,
708 default_multi=default_multi,
710 callback_params=callback_params,
714 validator_args=validator_args,
715 properties=properties)
717 def _validate(self, value):
718 ip = IP('{0}/32'.format(value))
719 if ip.iptype() == 'RESERVED':
720 raise ValueError(_("IP mustn't not be in reserved class"))
721 if self._only_private and not ip.iptype() == 'PRIVATE':
722 raise ValueError(_("IP must be in private class"))
725 class PortOption(Option):
726 """represents the choice of a port
727 The port numbers are divided into three ranges:
728 the well-known ports,
729 the registered ports,
730 and the dynamic or private ports.
731 You can actived this three range.
732 Port number 0 is reserved and can't be used.
733 see: http://en.wikipedia.org/wiki/Port_numbers
735 __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
738 def __init__(self, name, doc, default=None, default_multi=None,
739 requires=None, multi=False, callback=None,
740 callback_params=None, validator=None, validator_args=None,
741 properties=None, allow_range=False, allow_zero=False,
742 allow_wellknown=True, allow_registred=True,
743 allow_private=False):
744 self._allow_range = allow_range
745 self._min_value = None
746 self._max_value = None
747 ports_min = [0, 1, 1024, 49152]
748 ports_max = [0, 1023, 49151, 65535]
750 for index, allowed in enumerate([allow_zero,
754 if self._min_value is None:
756 self._min_value = ports_min[index]
759 elif allowed and is_finally:
760 raise ValueError(_('inconsistency in allowed range'))
762 self._max_value = ports_max[index]
764 if self._max_value is None:
765 raise ValueError(_('max value is empty'))
767 super(PortOption, self).__init__(name, doc, default=default,
768 default_multi=default_multi,
770 callback_params=callback_params,
774 validator_args=validator_args,
775 properties=properties)
777 def _validate(self, value):
778 if self._allow_range and ":" in str(value):
779 value = str(value).split(':')
781 raise ValueError('range must have two values only')
782 if not value[0] < value[1]:
783 raise ValueError('first port in range must be'
784 ' smaller than the second one')
789 if not self._min_value <= int(val) <= self._max_value:
790 raise ValueError('port must be an between {0} and {1}'
791 ''.format(self._min_value, self._max_value))
794 class NetworkOption(Option):
795 "represents the choice of a network"
797 _opt_type = 'network'
799 def _validate(self, value):
801 if ip.iptype() == 'RESERVED':
802 raise ValueError(_("network mustn't not be in reserved class"))
805 class NetmaskOption(Option):
806 "represents the choice of a netmask"
808 _opt_type = 'netmask'
810 def _validate(self, value):
811 IP('0.0.0.0/{0}'.format(value))
813 def _cons_network_netmask(self, optname, value, value_):
814 #opts must be (netmask, network) options
815 self.__cons_netmask(optname, value, value_, False)
817 def _cons_ip_netmask(self, optname, value, value_):
818 #opts must be (netmask, ip) options
819 self.__cons_netmask(optname, value, value_, True)
821 #def __cons_netmask(self, opt, value, context, index, opts, make_net):
822 def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
825 ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
827 #if cidr == 32, ip same has network
828 if ip.prefixlen() != 32:
830 IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
831 make_net=not make_net)
834 msg = _("invalid network {0} ({1}) "
835 "with netmask {2} ({3}),"
836 " this network is an IP")
839 msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
840 " this IP is a network")
844 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
846 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
848 raise ValueError(msg.format(val_ipnetwork, optname,
849 val_netmask, self._name))
852 class DomainnameOption(Option):
853 "represents the choice of a domain name"
854 __slots__ = ('_type', '_allow_ip')
855 _opt_type = 'domainname'
857 def __init__(self, name, doc, default=None, default_multi=None,
858 requires=None, multi=False, callback=None,
859 callback_params=None, validator=None, validator_args=None,
860 properties=None, allow_ip=False, type_='domainname'):
861 #netbios: for MS domain
862 #hostname: to identify the device
864 #fqdn: with tld, not supported yet
865 if type_ not in ['netbios', 'hostname', 'domainname']:
866 raise ValueError(_('unknown type_ {0} for hostname').format(type_))
868 if allow_ip not in [True, False]:
869 raise ValueError(_('allow_ip must be a boolean'))
870 self._allow_ip = allow_ip
871 super(DomainnameOption, self).__init__(name, doc, default=default,
872 default_multi=default_multi,
874 callback_params=callback_params,
878 validator_args=validator_args,
879 properties=properties)
881 def _validate(self, value):
882 if self._allow_ip is True:
884 IP('{0}/32'.format(value))
888 if self._type == 'netbios':
891 elif self._type == 'hostname':
894 elif self._type == 'domainname':
898 raise ValueError(_("invalid value for {0}, must have dot"
899 "").format(self._name))
900 if len(value) > length:
901 raise ValueError(_("invalid domainname's length for"
902 " {0} (max {1})").format(self._name, length))
904 raise ValueError(_("invalid domainname's length for {0} (min 2)"
905 "").format(self._name))
906 regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
907 if re.match(regexp, value) is None:
908 raise ValueError(_('invalid domainname'))
911 class OptionDescription(BaseOption):
912 """Config's schema (organisation, group) and container of Options
913 The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
915 __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
916 '_state_group_type', '_properties', '_children',
917 '_consistencies', '_calc_properties', '__weakref__',
918 '_readonly', '_impl_informations', '_state_requires',
919 '_state_consistencies', '_stated', '_state_readonly')
920 _opt_type = 'optiondescription'
922 def __init__(self, name, doc, children, requires=None, properties=None):
924 :param children: a list of options (including optiondescriptions)
927 super(OptionDescription, self).__init__(name, doc, requires, properties)
928 child_names = [child._name for child in children]
929 #better performance like this
930 valid_child = copy(child_names)
933 for child in valid_child:
935 raise ConflictError(_('duplicate option name: '
936 '{0}').format(child))
938 self._children = (tuple(child_names), tuple(children))
939 self._cache_paths = None
940 # the group_type is useful for filtering OptionDescriptions in a config
941 self._group_type = groups.default
943 def impl_getdoc(self):
944 return self.impl_get_information('doc')
946 def __getattr__(self, name):
947 if name in self.__slots__:
948 return object.__getattribute__(self, name)
950 return self._children[1][self._children[0].index(name)]
952 raise AttributeError(_('unknown Option {0} '
953 'in OptionDescription {1}'
954 '').format(name, self._name))
956 def impl_getkey(self, config):
957 return tuple([child.impl_getkey(getattr(config, child._name))
958 for child in self.impl_getchildren()])
960 def impl_getpaths(self, include_groups=False, _currpath=None):
961 """returns a list of all paths in self, recursively
962 _currpath should not be provided (helps with recursion)
964 if _currpath is None:
967 for option in self.impl_getchildren():
969 if isinstance(option, OptionDescription):
971 paths.append('.'.join(_currpath + [attr]))
972 paths += option.impl_getpaths(include_groups=include_groups,
973 _currpath=_currpath + [attr])
975 paths.append('.'.join(_currpath + [attr]))
978 def impl_getchildren(self):
979 return self._children[1]
981 def impl_build_cache(self,
986 force_no_consistencies=False):
987 if _currpath is None and self._cache_paths is not None:
990 if _currpath is None:
993 if not force_no_consistencies:
997 if cache_path is None:
1000 for option in self.impl_getchildren():
1002 if option in cache_option:
1003 raise ConflictError(_('duplicate option: {0}').format(option))
1005 cache_option.append(option)
1006 if not force_no_consistencies:
1007 option._readonly = True
1008 cache_path.append(str('.'.join(_currpath + [attr])))
1009 if not isinstance(option, OptionDescription):
1010 if not force_no_consistencies and \
1011 option._consistencies is not None:
1012 for consistency in option._consistencies:
1013 func, opt = consistency
1014 opts = (option, opt)
1015 _consistencies.setdefault(opt,
1016 []).append((func, opts))
1017 _consistencies.setdefault(option,
1018 []).append((func, opts))
1020 _currpath.append(attr)
1021 option.impl_build_cache(cache_path,
1025 force_no_consistencies)
1028 self._cache_paths = (tuple(cache_option), tuple(cache_path))
1029 if not force_no_consistencies:
1030 self._consistencies = _consistencies
1031 self._readonly = True
1033 def impl_get_opt_by_path(self, path):
1035 return self._cache_paths[0][self._cache_paths[1].index(path)]
1037 raise AttributeError(_('no option for path {0}').format(path))
1039 def impl_get_path_by_opt(self, opt):
1041 return self._cache_paths[1][self._cache_paths[0].index(opt)]
1043 raise AttributeError(_('no option {0} found').format(opt))
1045 # ____________________________________________________________
1046 def impl_set_group_type(self, group_type):
1047 """sets a given group object to an OptionDescription
1049 :param group_type: an instance of `GroupType` or `MasterGroupType`
1050 that lives in `setting.groups`
1052 if self._group_type != groups.default:
1053 raise TypeError(_('cannot change group_type if already set '
1054 '(old {0}, new {1})').format(self._group_type,
1056 if isinstance(group_type, groups.GroupType):
1057 self._group_type = group_type
1058 if isinstance(group_type, groups.MasterGroupType):
1059 #if master (same name has group) is set
1060 identical_master_child_name = False
1061 #for collect all slaves
1064 for child in self.impl_getchildren():
1065 if isinstance(child, OptionDescription):
1066 raise ValueError(_("master group {0} shall not have "
1067 "a subgroup").format(self._name))
1068 if isinstance(child, SymLinkOption):
1069 raise ValueError(_("master group {0} shall not have "
1070 "a symlinkoption").format(self._name))
1071 if not child.impl_is_multi():
1072 raise ValueError(_("not allowed option {0} "
1074 ": this option is not a multi"
1075 "").format(child._name, self._name))
1076 if child._name == self._name:
1077 identical_master_child_name = True
1078 child._multitype = multitypes.master
1081 slaves.append(child)
1083 raise ValueError(_('master group with wrong'
1084 ' master name for {0}'
1085 ).format(self._name))
1086 master._master_slaves = tuple(slaves)
1087 for child in self.impl_getchildren():
1089 child._master_slaves = master
1090 child._multitype = multitypes.slave
1091 if not identical_master_child_name:
1092 raise ValueError(_("no child has same nom has master group"
1093 " for: {0}").format(self._name))
1095 raise ValueError(_('group_type: {0}'
1096 ' not allowed').format(group_type))
1098 def impl_get_group_type(self):
1099 return self._group_type
1101 def _valid_consistency(self, opt, value, context=None, index=None):
1102 consistencies = self._consistencies.get(opt)
1103 if consistencies is not None:
1104 for consistency in consistencies:
1105 opt_ = consistency[1]
1106 ret = opt_[0]._launch_consistency(consistency[0],
1116 def _impl_getstate(self, descr=None):
1117 """enables us to export into a dict
1118 :param descr: parent :class:`tiramisu.option.OptionDescription`
1121 self.impl_build_cache()
1123 super(OptionDescription, self)._impl_getstate(descr)
1124 self._state_group_type = str(self._group_type)
1125 for option in self.impl_getchildren():
1126 option._impl_getstate(descr)
1128 def __getstate__(self):
1129 """special method to enable the serialization with pickle
1133 # the `_state` attribute is a flag that which tells us if
1134 # the serialization can be performed
1136 except AttributeError:
1137 # if cannot delete, _impl_getstate never launch
1138 # launch it recursivement
1139 # _stated prevent __getstate__ launch more than one time
1140 # _stated is delete, if re-serialize, re-lauch _impl_getstate
1141 self._impl_getstate()
1143 return super(OptionDescription, self).__getstate__(stated)
1145 def _impl_setstate(self, descr=None):
1146 """enables us to import from a dict
1147 :param descr: parent :class:`tiramisu.option.OptionDescription`
1150 self._cache_paths = None
1151 self.impl_build_cache(force_no_consistencies=True)
1153 self._group_type = getattr(groups, self._state_group_type)
1154 del(self._state_group_type)
1155 super(OptionDescription, self)._impl_setstate(descr)
1156 for option in self.impl_getchildren():
1157 option._impl_setstate(descr)
1159 def __setstate__(self, state):
1160 super(OptionDescription, self).__setstate__(state)
1163 except AttributeError:
1164 self._impl_setstate()
1167 def validate_requires_arg(requires, name):
1168 """check malformed requirements
1169 and tranform dict to internal tuple
1171 :param requires: have a look at the
1172 :meth:`tiramisu.setting.Settings.apply_requires` method to
1174 the description of the requires dictionary
1176 if requires is None:
1181 # start parsing all requires given by user (has dict)
1182 # transforme it to a tuple
1183 for require in requires:
1184 if not type(require) == dict:
1185 raise ValueError(_("malformed requirements type for option:"
1186 " {0}, must be a dict").format(name))
1187 valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1189 unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1190 if unknown_keys != frozenset():
1191 raise ValueError('malformed requirements for option: {0}'
1192 ' unknown keys {1}, must only '
1196 # prepare all attributes
1198 option = require['option']
1199 expected = require['expected']
1200 action = require['action']
1202 raise ValueError(_("malformed requirements for option: {0}"
1203 " require must have option, expected and"
1204 " action keys").format(name))
1205 inverse = require.get('inverse', False)
1206 if inverse not in [True, False]:
1207 raise ValueError(_('malformed requirements for option: {0}'
1208 ' inverse must be boolean'))
1209 transitive = require.get('transitive', True)
1210 if transitive not in [True, False]:
1211 raise ValueError(_('malformed requirements for option: {0}'
1212 ' transitive must be boolean'))
1213 same_action = require.get('same_action', True)
1214 if same_action not in [True, False]:
1215 raise ValueError(_('malformed requirements for option: {0}'
1216 ' same_action must be boolean'))
1218 if not isinstance(option, Option):
1219 raise ValueError(_('malformed requirements '
1220 'must be an option in option {0}').format(name))
1221 if option.impl_is_multi():
1222 raise ValueError(_('malformed requirements option {0} '
1223 'should not be a multi').format(name))
1224 if expected is not None:
1226 option._validate(expected)
1227 except ValueError as err:
1228 raise ValueError(_('malformed requirements second argument '
1229 'must be valid for option {0}'
1230 ': {1}').format(name, err))
1231 if action in config_action:
1232 if inverse != config_action[action]:
1233 raise ValueError(_("inconsistency in action types"
1235 " action: {1}").format(name, action))
1237 config_action[action] = inverse
1238 if action not in ret_requires:
1239 ret_requires[action] = {}
1240 if option not in ret_requires[action]:
1241 ret_requires[action][option] = (option, [expected], action,
1242 inverse, transitive, same_action)
1244 ret_requires[action][option][1].append(expected)
1245 # transform dict to tuple
1247 for opt_requires in ret_requires.values():
1249 for require in opt_requires.values():
1250 ret_action.append((require[0], tuple(require[1]), require[2],
1251 require[3], require[4], require[5]))
1252 ret.append(tuple(ret_action))
1253 return frozenset(config_action.keys()), tuple(ret)