tiramisu/autolib.py: optimpl_ => impl_
[tiramisu.git] / tiramisu / option.py
1 # -*- coding: utf-8 -*-
2 "option types and option description for the configuration management"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 import re
24 from copy import copy, deepcopy
25 from types import FunctionType
26 from IPy import IP
27
28 from tiramisu.error import ConflictError
29 from tiramisu.setting import groups, multitypes
30 from tiramisu.i18n import _
31 from tiramisu.autolib import carry_out_calculation
32
33 name_regexp = re.compile(r'^\d+')
34 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
35                    'make_dict', 'unwrap_from_path', 'read_only',
36                    'read_write', 'getowner', 'set_contexts')
37
38
39 def valid_name(name):
40     try:
41         name = str(name)
42     except:
43         return False
44     if re.match(name_regexp, name) is None and not name.startswith('_') \
45             and name not in forbidden_names \
46             and not name.startswith('impl_') \
47             and not name.startswith('cfgimpl_'):
48         return True
49     else:
50         return False
51 #____________________________________________________________
52 #
53
54
55 class BaseInformation(object):
56     __slots__ = ('_impl_informations',)
57
58     def impl_set_information(self, key, value):
59         """updates the information's attribute
60         (wich is a dictionnary)
61
62         :param key: information's key (ex: "help", "doc"
63         :param value: information's value (ex: "the help string")
64         """
65         self._impl_informations[key] = value
66
67     def impl_get_information(self, key, default=None):
68         """retrieves one information's item
69
70         :param key: the item string (ex: "help")
71         """
72         if key in self._impl_informations:
73             return self._impl_informations[key]
74         elif default is not None:
75             return default
76         else:
77             raise ValueError(_("Information's item not found: {0}").format(key))
78
79
80 class Option(BaseInformation):
81     """
82     Abstract base class for configuration option's.
83
84     Reminder: an Option object is **not** a container for the value
85     """
86     __slots__ = ('_name', '_requires', '_multi', '_validator', '_default_multi',
87                  '_default', '_properties', '_callback', '_multitype',
88                  '_master_slaves', '_consistencies', '_empty')
89     _empty = ''
90
91     def __init__(self, name, doc, default=None, default_multi=None,
92                  requires=None, multi=False, callback=None,
93                  callback_params=None, validator=None, validator_args=None,
94                  properties=None):
95         """
96         :param name: the option's name
97         :param doc: the option's description
98         :param default: specifies the default value of the option,
99                         for a multi : ['bla', 'bla', 'bla']
100         :param default_multi: 'bla' (used in case of a reset to default only at
101                         a given index)
102         :param requires: is a list of names of options located anywhere
103                          in the configuration.
104         :param multi: if true, the option's value is a list
105         :param callback: the name of a function. If set, the function's output
106                          is responsible of the option's value
107         :param callback_params: the callback's parameter
108         :param validator: the name of a function wich stands for a custom
109                           validation of the value
110         :param validator_args: the validator's parameters
111         """
112         if not valid_name(name):
113             raise ValueError(_("invalid name: {0} for option").format(name))
114         self._name = name
115         self._impl_informations = {}
116         self.impl_set_information('doc', doc)
117         validate_requires_arg(requires, self._name)
118         self._requires = requires
119         self._multi = multi
120         self._consistencies = None
121         if validator is not None:
122             if type(validator) != FunctionType:
123                 raise TypeError(_("validator must be a function"))
124             if validator_args is None:
125                 validator_args = {}
126             self._validator = (validator, validator_args)
127         else:
128             self._validator = None
129         if not self._multi and default_multi is not None:
130             raise ValueError(_("a default_multi is set whereas multi is False"
131                              " in option: {0}").format(name))
132         if default_multi is not None and not self._validate(default_multi):
133             raise ValueError(_("invalid default_multi value {0} "
134                              "for option {1}").format(str(default_multi), name))
135         if callback is not None and (default is not None or default_multi is not None):
136             raise ValueError(_("defaut values not allowed if option: {0} "
137                              "is calculated").format(name))
138         if callback is None and callback_params is not None:
139             raise ValueError(_("params defined for a callback function but "
140                              "no callback defined yet for option {0}").format(name))
141         if callback is not None:
142             if type(callback) != FunctionType:
143                 raise ValueError('callback must be a function')
144             if callback_params is not None and \
145                     not isinstance(callback_params, dict):
146                 raise ValueError('callback_params must be a dict')
147             self._callback = (callback, callback_params)
148         else:
149             self._callback = None
150         if self._multi:
151             if default is None:
152                 default = []
153             self._multitype = multitypes.default
154             self._default_multi = default_multi
155         self.impl_validate(default)
156         self._default = default
157         if properties is None:
158             properties = tuple()
159         if not isinstance(properties, tuple):
160             raise TypeError(_('invalid properties type {0} for {1},'
161                             ' must be a tuple').format(type(properties), self._name))
162         self._properties = set(properties)  # 'hidden', 'disabled'...
163
164     def __eq__(self, other):
165         "Option comparison"
166         if not isinstance(other, Option):
167             return False
168         slots = list(self.__slots__ + Option.__slots__ + BaseInformation.__slots__)
169         for var in slots:
170             try:
171                 val1 = getattr(self, var)
172                 not_in1 = False
173             except:
174                 not_in1 = True
175             try:
176                 val2 = getattr(other, var)
177                 not_in2 = False
178             except:
179                 not_in2 = True
180             if True in (not_in1, not_in2):
181                 if not_in1 != not_in2:
182                     return False
183             elif val1 != val2:
184                 return False
185         return True
186
187     def __ne__(self, other):
188         if not isinstance(other, Option):
189             return False
190         return not self == other
191
192     def _launch_consistency(self, func, opt, vals, context, index, opt_):
193         if context is not None:
194             descr = context.cfgimpl_get_description()
195         if opt is self:
196             #values are for self, search opt_ values
197             values = vals
198             if context is not None:
199                 path = descr.impl_get_path_by_opt(opt_)
200                 values_ = context._getattr(path, validate=False)
201             else:
202                 values_ = opt_.impl_getdefault()
203             if index is not None:
204                 #value is not already set, could be higher
205                 try:
206                     values_ = values_[index]
207                 except IndexError:
208                     values_ = None
209         else:
210             #values are for opt_, search self values
211             values_ = vals
212             if context is not None:
213                 path = descr.impl_get_path_by_opt(self)
214                 values = context._getattr(path, validate=False)
215             else:
216                 values = self.impl_getdefault()
217             if index is not None:
218                 #value is not already set, could be higher
219                 try:
220                     values = values[index]
221                 except IndexError:
222                     values = None
223         if index is None and self.impl_is_multi():
224             for index in range(0, len(values)):
225                 try:
226                     value = values[index]
227                     value_ = values_[index]
228                 except IndexError:
229                     value = None
230                     value_ = None
231                 if None not in (value, value_):
232                     getattr(self, func)(opt_._name, value, value_)
233         else:
234             if None not in (values, values_):
235                 getattr(self, func)(opt_._name, values, values_)
236
237     def impl_validate(self, value, context=None, validate=True):
238         """
239         :param value: the option's value
240         :param validate: if true enables ``self._validator`` validation
241         """
242         def _val_validator(val):
243             callback_params = deepcopy(self._validator[1])
244             callback_params.setdefault('', []).insert(0, val)
245             return carry_out_calculation(self._name, config=context,
246                                          callback=self._validator[0],
247                                          callback_params=callback_params)
248
249         def val_validator():
250             #add current value has first argument
251             if self.impl_is_multi():
252                 for val in value:
253                     if not _val_validator(val):
254                         return False
255                 return True
256             else:
257                 return _val_validator(value)
258         # generic calculation
259         if context is not None:
260             descr = context.cfgimpl_get_description()
261         if not self._multi:
262             if value is not None and ((validate and
263                                       self._validator is not None and
264                                       not val_validator()) or
265                                       not self._validate(value)):
266                 raise ValueError(_("invalid value {0} for option {1}"
267                                    "").format(value, self._name))
268             if context is not None:
269                 descr._valid_consistency(self, value, context, None)
270         else:
271             if not isinstance(value, list):
272                 raise ValueError(_("invalid value {0} for option {1} "
273                                    "which must be a list").format(value,
274                                                                   self._name))
275             for index in range(0, len(value)):
276                 val = value[index]
277                 if val is not None and ((validate and
278                                          self._validator is not None and
279                                          not val_validator()) or
280                                         not self._validate(val)):
281                         raise ValueError(_("invalid value {0} for option {1}"
282                                            "").format(value, self._name))
283                 if context is not None:
284                     descr._valid_consistency(self, val, context, index)
285
286     def impl_getdefault(self, default_multi=False):
287         "accessing the default value"
288         if not default_multi or not self.impl_is_multi():
289             return self._default
290         else:
291             return self.getdefault_multi()
292
293     def impl_getdefault_multi(self):
294         "accessing the default value for a multi"
295         return self._default_multi
296
297     def impl_get_multitype(self):
298         return self._multitype
299
300     def impl_get_master_slaves(self):
301         return self._master_slaves
302
303     def impl_is_empty_by_default(self):
304         "no default value has been set yet"
305         if ((not self.impl_is_multi() and self._default is None) or
306                 (self.impl_is_multi() and (self._default == [] or None in self._default))):
307             return True
308         return False
309
310     def impl_getdoc(self):
311         "accesses the Option's doc"
312         return self.impl_get_information('doc')
313
314     def impl_has_callback(self):
315         "to know if a callback has been defined or not"
316         if self._callback is None:
317             return False
318         else:
319             return True
320
321     def impl_getkey(self, value):
322         return value
323
324     def impl_is_multi(self):
325         return self._multi
326
327     def impl_add_consistency(self, func, opt):
328         if self._consistencies is None:
329             self._consistencies = []
330         if not isinstance(opt, Option):
331             raise ValueError('consistency must be set with an option')
332         if self is opt:
333             raise ValueError('cannot add consistency with itself')
334         func = '_cons_{}'.format(func)
335         self._launch_consistency(func, self, self.impl_getdefault(), None, None, opt)
336         self._consistencies.append((func, opt))
337         self.impl_validate(self.impl_getdefault(), None)
338
339     def _cons_not_equal(self, optname, value, value_):
340         if value == value_:
341             raise ValueError(_("invalid value {0} for option {1} "
342                                "must be different as {2} option"
343                                "").format(value, self._name, optname))
344
345
346 class ChoiceOption(Option):
347     __slots__ = ('_values', '_open_values', '_opt_type')
348     _opt_type = 'string'
349
350     def __init__(self, name, doc, values, default=None, default_multi=None,
351                  requires=None, multi=False, callback=None,
352                  callback_params=None, open_values=False, validator=None,
353                  validator_args=None, properties=()):
354         if not isinstance(values, tuple):
355             raise TypeError(_('values must be a tuple for {0}').format(name))
356         self._values = values
357         if open_values not in (True, False):
358             raise TypeError(_('open_values must be a boolean for '
359                             '{0}').format(name))
360         self._open_values = open_values
361         super(ChoiceOption, self).__init__(name, doc, default=default,
362                                            default_multi=default_multi,
363                                            callback=callback,
364                                            callback_params=callback_params,
365                                            requires=requires,
366                                            multi=multi,
367                                            validator=validator,
368                                            validator_args=validator_args,
369                                            properties=properties)
370
371     def impl_get_values(self):
372         return self._values
373
374     def impl_is_openvalues(self):
375         return self._open_values
376
377     def _validate(self, value):
378         if not self._open_values:
379             return value is None or value in self._values
380         else:
381             return True
382
383
384 class BoolOption(Option):
385     __slots__ = ('_opt_type',)
386     _opt_type = 'bool'
387
388     def _validate(self, value):
389         return isinstance(value, bool)
390
391
392 class IntOption(Option):
393     __slots__ = ('_opt_type',)
394     _opt_type = 'int'
395
396     def _validate(self, value):
397         return isinstance(value, int)
398
399
400 class FloatOption(Option):
401     __slots__ = ('_opt_type',)
402     _opt_type = 'float'
403
404     def _validate(self, value):
405         return isinstance(value, float)
406
407
408 class StrOption(Option):
409     __slots__ = ('_opt_type',)
410     _opt_type = 'string'
411
412     def _validate(self, value):
413         return isinstance(value, str)
414
415
416 class UnicodeOption(Option):
417     __slots__ = ('_opt_type',)
418     _opt_type = 'unicode'
419     _empty = u''
420
421     def _validate(self, value):
422         return isinstance(value, unicode)
423
424
425 class SymLinkOption(object):
426     __slots__ = ('_name', '_opt', '_consistencies')
427     _opt_type = 'symlink'
428     _consistencies = None
429
430     def __init__(self, name, opt):
431         self._name = name
432         if not isinstance(opt, Option):
433             raise ValueError(_('malformed symlinkoption '
434                                'must be an option for symlink {0}').format(name))
435         self._opt = opt
436
437     def __getattr__(self, name):
438         if name in ('_name', '_opt', '_consistencies'):
439             return object.__gettattr__(self, name)
440         else:
441             return getattr(self._opt, name)
442
443
444 class IPOption(Option):
445     __slots__ = ('_opt_type', '_only_private')
446     _opt_type = 'ip'
447
448     def __init__(self, name, doc, default=None, default_multi=None,
449                  requires=None, multi=False, callback=None,
450                  callback_params=None, validator=None, validator_args=None,
451                  properties=None, only_private=False):
452         super(IPOption, self).__init__(name, doc, default=default,
453                                        default_multi=default_multi,
454                                        callback=callback,
455                                        callback_params=callback_params,
456                                        requires=requires,
457                                        multi=multi,
458                                        validator=validator,
459                                        validator_args=validator_args,
460                                        properties=properties)
461         self._only_private = only_private
462
463     def _validate(self, value):
464         try:
465             ip = IP('{0}/32'.format(value))
466             if self._only_private:
467                 return ip.iptype() == 'PRIVATE'
468             return True
469         except ValueError:
470             return False
471
472
473 class NetworkOption(Option):
474     __slots__ = ('_opt_type',)
475     _opt_type = 'network'
476
477     def _validate(self, value):
478         try:
479             IP(value)
480             return True
481         except ValueError:
482             return False
483
484
485 class NetmaskOption(Option):
486     __slots__ = ('_opt_type',)
487     _opt_type = 'netmask'
488
489     def _validate(self, value):
490         try:
491             IP('0.0.0.0/{}'.format(value))
492             return True
493         except ValueError:
494             return False
495
496     def _cons_network_netmask(self, optname, value, value_):
497         #opts must be (netmask, network) options
498         self.__cons_netmask(optname, value, value_, False)
499
500     def _cons_ip_netmask(self, optname, value, value_):
501         #opts must be (netmask, ip) options
502         self.__cons_netmask(optname, value, value_, True)
503
504     #def __cons_netmask(self, opt, value, context, index, opts, make_net):
505     def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
506         msg = None
507         try:
508             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
509                     make_net=make_net)
510             #if cidr == 32, ip same has network
511             if ip.prefixlen() != 32:
512                 try:
513                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
514                         make_net=not make_net)
515                 except ValueError:
516                     if not make_net:
517                         msg = _("invalid network {0} ({1}) with netmask {2} ({3}),"
518                                 " this network is an ip")
519                 else:
520                     if make_net:
521                         msg = _("invalid ip {0} ({1}) with netmask {2} ({3}),"
522                                 " this ip is a network")
523
524         except ValueError:
525             if make_net:
526                 msg = _("invalid ip {0} ({1}) with netmask {2} ({3})")
527             else:
528                 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
529         if msg is not None:
530             raise ValueError(msg.format(val_ipnetwork, optname,
531                                         val_netmask, self._name))
532
533
534 class DomainnameOption(Option):
535     __slots__ = ('_opt_type', '_type', '_allow_ip')
536     _opt_type = 'domainname'
537
538     def __init__(self, name, doc, default=None, default_multi=None,
539                  requires=None, multi=False, callback=None,
540                  callback_params=None, validator=None, validator_args=None,
541                  properties=None, allow_ip=False, type_='domainname'):
542         #netbios: for MS domain
543         #hostname: to identify the device
544         #domainname:
545         #fqdn: with tld, not supported yet
546         if type_ not in ['netbios', 'hostname', 'domainname']:
547             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
548         self._type = type_
549         if allow_ip not in [True, False]:
550             raise ValueError(_('allow_ip must be a boolean'))
551         self._allow_ip = allow_ip
552         super(DomainnameOption, self).__init__(name, doc, default=default,
553                                                default_multi=default_multi,
554                                                callback=callback,
555                                                callback_params=callback_params,
556                                                requires=requires,
557                                                multi=multi,
558                                                validator=validator,
559                                                validator_args=validator_args,
560                                                properties=properties)
561
562     def _validate(self, value):
563         if self._allow_ip is True:
564             try:
565                 IP('{0}/32'.format(value))
566                 return True
567             except ValueError:
568                 pass
569         if self._type == 'netbios':
570             length = 15
571             extrachar = ''
572         elif self._type == 'hostname':
573             length = 63
574             extrachar = ''
575         elif self._type == 'domainname':
576             length = 255
577             extrachar = '\.'
578             if '.' not in value:
579                 raise ValueError(_("invalid value for {0}, must have dot"
580                                    "").format(self._name))
581         if len(value) > length:
582             raise ValueError(_("invalid value's length for {0} (max {1})"
583                                "").format(self._name, length))
584         regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
585         return re.match(regexp, value) is not None
586
587
588 class OptionDescription(BaseInformation):
589     """Config's schema (organisation, group) and container of Options"""
590     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
591                  '_properties', '_children', '_consistencies')
592
593     def __init__(self, name, doc, children, requires=None, properties=None):
594         """
595         :param children: is a list of option descriptions (including
596         ``OptionDescription`` instances for nested namespaces).
597         """
598         if not valid_name(name):
599             raise ValueError(_("invalid name: {0} for option descr").format(name))
600         self._name = name
601         self._impl_informations = {}
602         self.impl_set_information('doc', doc)
603         child_names = [child._name for child in children]
604         #better performance like this
605         valid_child = copy(child_names)
606         valid_child.sort()
607         old = None
608         for child in valid_child:
609             if id(child) == id(old):
610                 raise ConflictError(_('duplicate option name: '
611                                       '{0}').format(child))
612             old = child
613         self._children = (tuple(child_names), tuple(children))
614         validate_requires_arg(requires, self._name)
615         self._requires = requires
616         self._cache_paths = None
617         self._consistencies = None
618         if properties is None:
619             properties = tuple()
620         if not isinstance(properties, tuple):
621             raise TypeError(_('invalid properties type {0} for {1},'
622                               ' must be a tuple').format(type(properties), self._name))
623         self._properties = set(properties)  # 'hidden', 'disabled'...
624         # the group_type is useful for filtering OptionDescriptions in a config
625         self._group_type = groups.default
626
627     def impl_getdoc(self):
628         return self.impl_get_information('doc')
629
630     def __getattr__(self, name):
631         try:
632             return self._children[1][self._children[0].index(name)]
633         except ValueError:
634             raise AttributeError(_('unknown Option {} in OptionDescription {}'
635                                  '').format(name, self._name))
636
637     def impl_getkey(self, config):
638         return tuple([child.impl_getkey(getattr(config, child._name))
639                       for child in self.impl_getchildren()])
640
641     def impl_getpaths(self, include_groups=False, _currpath=None):
642         """returns a list of all paths in self, recursively
643            _currpath should not be provided (helps with recursion)
644         """
645         if _currpath is None:
646             _currpath = []
647         paths = []
648         for option in self.impl_getchildren():
649             attr = option._name
650             if isinstance(option, OptionDescription):
651                 if include_groups:
652                     paths.append('.'.join(_currpath + [attr]))
653                 paths += option.impl_getpaths(include_groups=include_groups,
654                                               _currpath=_currpath + [attr])
655             else:
656                 paths.append('.'.join(_currpath + [attr]))
657         return paths
658
659     def impl_getchildren(self):
660         return self._children[1]
661
662     def impl_build_cache(self, cache_path=None, cache_option=None, _currpath=None, _consistencies=None):
663         if _currpath is None and self._cache_paths is not None:
664             return
665         if _currpath is None:
666             save = True
667             _currpath = []
668             _consistencies = {}
669         else:
670             save = False
671         if cache_path is None:
672             cache_path = [self._name]
673             cache_option = [self]
674         for option in self.impl_getchildren():
675             attr = option._name
676             if attr.startswith('_cfgimpl'):
677                 continue
678             cache_option.append(option)
679             cache_path.append(str('.'.join(_currpath + [attr])))
680             if not isinstance(option, OptionDescription):
681                 if option._consistencies is not None:
682                     for consistency in option._consistencies:
683                         func, opt = consistency
684                         opts = (option, opt)
685                         _consistencies.setdefault(opt, []).append((func, opts))
686                         _consistencies.setdefault(option, []).append((func, opts))
687             else:
688                 _currpath.append(attr)
689                 option.impl_build_cache(cache_path, cache_option, _currpath, _consistencies)
690                 _currpath.pop()
691         if save:
692             #valid no duplicated option
693             valid_child = copy(cache_option)
694             valid_child.sort()
695             old = None
696             for child in valid_child:
697                 if id(child) == id(old):
698                     raise ConflictError(_('duplicate option: '
699                                           '{0}').format(child))
700                 old = child
701             self._cache_paths = (tuple(cache_option), tuple(cache_path))
702             self._consistencies = _consistencies
703
704     def impl_get_opt_by_path(self, path):
705         try:
706             return self._cache_paths[0][self._cache_paths[1].index(path)]
707         except ValueError:
708             raise AttributeError(_('no option for path {}').format(path))
709
710     def impl_get_path_by_opt(self, opt):
711         try:
712             return self._cache_paths[1][self._cache_paths[0].index(opt)]
713         except ValueError:
714             raise AttributeError(_('no option {} found').format(opt))
715
716     # ____________________________________________________________
717     def impl_set_group_type(self, group_type):
718         """sets a given group object to an OptionDescription
719
720         :param group_type: an instance of `GroupType` or `MasterGroupType`
721                               that lives in `setting.groups`
722         """
723         if self._group_type != groups.default:
724             raise TypeError(_('cannot change group_type if already set '
725                             '(old {}, new {})').format(self._group_type, group_type))
726         if isinstance(group_type, groups.GroupType):
727             self._group_type = group_type
728             if isinstance(group_type, groups.MasterGroupType):
729                 #if master (same name has group) is set
730                 identical_master_child_name = False
731                 #for collect all slaves
732                 slaves = []
733                 master = None
734                 for child in self.impl_getchildren():
735                     if isinstance(child, OptionDescription):
736                         raise ValueError(_("master group {} shall not have "
737                                          "a subgroup").format(self._name))
738                     if not child.impl_is_multi():
739                         raise ValueError(_("not allowed option {0} in group {1}"
740                                          ": this option is not a multi"
741                                          "").format(child._name, self._name))
742                     if child._name == self._name:
743                         identical_master_child_name = True
744                         child._multitype = multitypes.master
745                         master = child
746                     else:
747                         slaves.append(child)
748                 if master is None:
749                     raise ValueError(_('master group with wrong master name for {}'
750                                      '').format(self._name))
751                 master._master_slaves = tuple(slaves)
752                 for child in self.impl_getchildren():
753                     if child != master:
754                         child._master_slaves = master
755                         child._multitype = multitypes.slave
756                 if not identical_master_child_name:
757                     raise ValueError(_("the master group: {} has not any "
758                                      "master child").format(self._name))
759         else:
760             raise ValueError(_('not allowed group_type : {0}').format(group_type))
761
762     def impl_get_group_type(self):
763         return self._group_type
764
765     def _valid_consistency(self, opt, value, context=None, index=None):
766         consistencies = self._consistencies.get(opt)
767         if consistencies is not None:
768             for consistency in consistencies:
769                 opt_ = consistency[1]
770                 ret = opt_[0]._launch_consistency(consistency[0], opt, value, context,
771                                                   index, opt_[1])
772                 if ret is False:
773                     return False
774         return True
775
776
777 def validate_requires_arg(requires, name):
778     "check malformed requirements"
779     if requires is not None:
780         config_action = {}
781         for req in requires:
782             if not type(req) == tuple:
783                 raise ValueError(_("malformed requirements type for option:"
784                                    " {0}, must be a tuple").format(name))
785             if not isinstance(req[0], Option):
786                 raise ValueError(_('malformed requirements first argument '
787                                    'must be an option in option {0}').format(name))
788             if req[0].impl_is_multi():
789                 raise ValueError(_('malformed requirements option {0} '
790                                    'should not be a multi').format(name))
791             if not req[0]._validate(req[1]):
792                 raise ValueError(_('malformed requirements second argument '
793                                    'must be valid for option {0}').format(name))
794             if len(req) == 3:
795                 action = req[2]
796                 inverse = False
797             elif len(req) == 4:
798                 action = req[2]
799                 inverse = req[3]
800             else:
801                 raise ValueError(_("malformed requirements for option: {0}"
802                                  " invalid len").format(name))
803             if action in config_action:
804                 if inverse != config_action[action]:
805                     raise ValueError(_("inconsistency in action types for option: {0}"
806                                      " action: {1}").format(name, action))
807             else:
808                 config_action[action] = inverse