# -*- coding: utf-8 -*- "option types and option description" # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # The original `Config` design model is unproudly borrowed from # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re import sys from copy import copy, deepcopy from types import FunctionType from IPy import IP from tiramisu.error import ConflictError from tiramisu.setting import groups, multitypes from tiramisu.i18n import _ from tiramisu.autolib import carry_out_calculation name_regexp = re.compile(r'^\d+') forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', 'make_dict', 'unwrap_from_path', 'read_only', 'read_write', 'getowner', 'set_contexts') def valid_name(name): "an option's name is a str and does not start with 'impl' or 'cfgimpl'" try: name = str(name) except: return False if re.match(name_regexp, name) is None and not name.startswith('_') \ and name not in forbidden_names \ and not name.startswith('impl_') \ and not name.startswith('cfgimpl_'): return True else: return False #____________________________________________________________ # class BaseInformation(object): "interface for an option's information attribute" __slots__ = ('_impl_informations',) def impl_set_information(self, key, value): """updates the information's attribute (which is a dictionary) :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ try: self._impl_informations[key] = value except AttributeError: raise AttributeError(_('{0} has no attribute ' 'impl_set_information').format( self.__class__.__name__)) def impl_get_information(self, key, default=None): """retrieves one information's item :param key: the item string (ex: "help") """ try: if key in self._impl_informations: return self._impl_informations[key] elif default is not None: return default else: raise ValueError(_("information's item" " not found: {0}").format(key)) except AttributeError: raise AttributeError(_('{0} has no attribute ' 'impl_get_information').format( self.__class__.__name__)) class _ReadOnlyOption(BaseInformation): __slots__ = ('_readonly',) def __setattr__(self, name, value): is_readonly = False # never change _name if name == '_name': try: self._name #so _name is already set is_readonly = True except: pass try: if self._readonly is True: if value is True: # already readonly and try to re set readonly # don't raise, just exit return is_readonly = True except AttributeError: pass if is_readonly: raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is" " read-only").format( self.__class__.__name__, self._name, name)) object.__setattr__(self, name, value) class Option(_ReadOnlyOption): """ Abstract base class for configuration option's. Reminder: an Option object is **not** a container for the value """ __slots__ = ('_name', '_requires', '_multi', '_validator', '_default_multi', '_default', '_properties', '_callback', '_multitype', '_master_slaves', '_consistencies', '_calc_properties', '__weakref__') _empty = '' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_args=None, properties=None): """ :param name: the option's name :param doc: the option's description :param default: specifies the default value of the option, for a multi : ['bla', 'bla', 'bla'] :param default_multi: 'bla' (used in case of a reset to default only at a given index) :param requires: is a list of names of options located anywhere in the configuration. :param multi: if true, the option's value is a list :param callback: the name of a function. If set, the function's output is responsible of the option's value :param callback_params: the callback's parameter :param validator: the name of a function wich stands for a custom validation of the value :param validator_args: the validator's parameters """ if not valid_name(name): raise ValueError(_("invalid name: {0} for option").format(name)) self._name = name self._impl_informations = {} self.impl_set_information('doc', doc) self._calc_properties, self._requires = validate_requires_arg( requires, self._name) self._multi = multi self._consistencies = None if validator is not None: if type(validator) != FunctionType: raise TypeError(_("validator must be a function")) if validator_args is None: validator_args = {} self._validator = (validator, validator_args) else: self._validator = None if not self._multi and default_multi is not None: raise ValueError(_("a default_multi is set whereas multi is False" " in option: {0}").format(name)) if default_multi is not None: try: self._validate(default_multi) except ValueError as err: raise ValueError(_("invalid default_multi value {0} " "for option {1}: {2}").format( str(default_multi), name, err)) if callback is not None and (default is not None or default_multi is not None): raise ValueError(_("default value not allowed if option: {0} " "is calculated").format(name)) if callback is None and callback_params is not None: raise ValueError(_("params defined for a callback function but " "no callback defined" " yet for option {0}").format(name)) if callback is not None: if type(callback) != FunctionType: raise ValueError('callback must be a function') if callback_params is not None and \ not isinstance(callback_params, dict): raise ValueError('callback_params must be a dict') self._callback = (callback, callback_params) else: self._callback = None if self._multi: if default is None: default = [] self._multitype = multitypes.default self._default_multi = default_multi self.impl_validate(default) self._default = default if properties is None: properties = tuple() if not isinstance(properties, tuple): raise TypeError(_('invalid properties type {0} for {1},' ' must be a tuple').format( type(properties), self._name)) self._properties = properties # 'hidden', 'disabled'... def _launch_consistency(self, func, opt, vals, context, index, opt_): if context is not None: descr = context.cfgimpl_get_description() if opt is self: #values are for self, search opt_ values values = vals if context is not None: path = descr.impl_get_path_by_opt(opt_) values_ = context._getattr(path, validate=False) else: values_ = opt_.impl_getdefault() if index is not None: #value is not already set, could be higher try: values_ = values_[index] except IndexError: values_ = None else: #values are for opt_, search self values values_ = vals if context is not None: path = descr.impl_get_path_by_opt(self) values = context._getattr(path, validate=False) else: values = self.impl_getdefault() if index is not None: #value is not already set, could be higher try: values = values[index] except IndexError: values = None if index is None and self.impl_is_multi(): for index in range(0, len(values)): try: value = values[index] value_ = values_[index] except IndexError: value = None value_ = None if None not in (value, value_): getattr(self, func)(opt_._name, value, value_) else: if None not in (values, values_): getattr(self, func)(opt_._name, values, values_) def impl_validate(self, value, context=None, validate=True): """ :param value: the option's value :param validate: if true enables ``self._validator`` validation """ if not validate: return def val_validator(val): if self._validator is not None: callback_params = deepcopy(self._validator[1]) callback_params.setdefault('', []).insert(0, val) return carry_out_calculation(self._name, config=context, callback=self._validator[0], callback_params=callback_params) else: return True def do_validation(_value, _index=None): if _value is None: return True if not val_validator(_value): raise ValueError(_("invalid value {0} " "for option {1} for object {2}" ).format(_value, self._name, self.__class__.__name__)) try: self._validate(_value) except ValueError as err: raise ValueError(_("invalid value {0} for option {1}: {2}" "").format(_value, self._name, err)) if context is not None: descr._valid_consistency(self, _value, context, _index) # generic calculation if context is not None: descr = context.cfgimpl_get_description() if not self._multi: do_validation(value) else: if not isinstance(value, list): raise ValueError(_("invalid value {0} for option {1} " "which must be a list").format(value, self._name)) for index in range(0, len(value)): val = value[index] do_validation(val, index) def impl_getdefault(self, default_multi=False): "accessing the default value" if not default_multi or not self.impl_is_multi(): return self._default else: return self.getdefault_multi() def impl_getdefault_multi(self): "accessing the default value for a multi" return self._default_multi def impl_get_multitype(self): return self._multitype def impl_get_master_slaves(self): return self._master_slaves def impl_is_empty_by_default(self): "no default value has been set yet" if ((not self.impl_is_multi() and self._default is None) or (self.impl_is_multi() and (self._default == [] or None in self._default))): return True return False def impl_getdoc(self): "accesses the Option's doc" return self.impl_get_information('doc') def impl_has_callback(self): "to know if a callback has been defined or not" if self._callback is None: return False else: return True def impl_getkey(self, value): return value def impl_is_multi(self): return self._multi def impl_add_consistency(self, func, opt): if self._consistencies is None: self._consistencies = [] if not isinstance(opt, Option): raise ValueError('consistency must be set with an option') if self is opt: raise ValueError('cannot add consistency with itself') if self.impl_is_multi() != opt.impl_is_multi(): raise ValueError('options in consistency' ' should be multi in two sides') func = '_cons_{0}'.format(func) self._launch_consistency(func, self, self.impl_getdefault(), None, None, opt) self._consistencies.append((func, opt)) self.impl_validate(self.impl_getdefault()) def _cons_not_equal(self, optname, value, value_): if value == value_: raise ValueError(_("invalid value {0} for option {1} " "must be different as {2} option" "").format(value, self._name, optname)) class ChoiceOption(Option): """represents a choice out of several objects. The option can also have the value ``None`` """ __slots__ = ('_values', '_open_values') _opt_type = 'string' def __init__(self, name, doc, values, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, open_values=False, validator=None, validator_args=None, properties=()): """ :param values: is a list of values the option can possibly take """ if not isinstance(values, tuple): raise TypeError(_('values must be a tuple for {0}').format(name)) self._values = values if open_values not in (True, False): raise TypeError(_('open_values must be a boolean for ' '{0}').format(name)) self._open_values = open_values super(ChoiceOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_args=validator_args, properties=properties) def impl_get_values(self): return self._values def impl_is_openvalues(self): return self._open_values def _validate(self, value): if not self._open_values and not value in self._values: raise ValueError(_('value {0} is not permitted, ' 'only {1} is allowed' '').format(value, self._values)) class BoolOption(Option): "represents a choice between ``True`` and ``False``" __slots__ = tuple() _opt_type = 'bool' def _validate(self, value): if not isinstance(value, bool): raise ValueError(_('value must be a boolean')) class IntOption(Option): "represents a choice of an integer" __slots__ = tuple() _opt_type = 'int' def _validate(self, value): if not isinstance(value, int): raise ValueError(_('value must be an integer')) class FloatOption(Option): "represents a choice of a floating point number" __slots__ = tuple() _opt_type = 'float' def _validate(self, value): if not isinstance(value, float): raise ValueError(_('value must be a float')) class StrOption(Option): "represents the choice of a string" __slots__ = tuple() _opt_type = 'string' def _validate(self, value): if not isinstance(value, str): raise ValueError(_('value must be a string, not ' '{0}').format(type(value))) if sys.version_info[0] >= 3: #UnicodeOption is same has StrOption in python 3+ class UnicodeOption(StrOption): __slots__ = tuple() pass else: class UnicodeOption(Option): "represents the choice of a unicode string" __slots__ = tuple() _opt_type = 'unicode' _empty = u'' def _validate(self, value): if not isinstance(value, unicode): raise ValueError(_('value must be an unicode')) class SymLinkOption(_ReadOnlyOption): __slots__ = ('_name', '_opt') _opt_type = 'symlink' def __init__(self, name, opt): self._name = name if not isinstance(opt, Option): raise ValueError(_('malformed symlinkoption ' 'must be an option ' 'for symlink {0}').format(name)) self._opt = opt self._readonly = True def __getattr__(self, name): if name in ('_name', '_opt', '_opt_type', '_readonly'): return object.__getattr__(self, name) else: return getattr(self._opt, name) class IPOption(Option): "represents the choice of an ip" __slots__ = ('_only_private',) _opt_type = 'ip' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_args=None, properties=None, only_private=False): self._only_private = only_private super(IPOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_args=validator_args, properties=properties) def _validate(self, value): ip = IP('{0}/32'.format(value)) if ip.iptype() == 'RESERVED': raise ValueError(_("IP mustn't not be in reserved class")) if self._only_private and not ip.iptype() == 'PRIVATE': raise ValueError(_("IP must be in private class")) class PortOption(Option): """represents the choice of a port The port numbers are divided into three ranges: the well-known ports, the registered ports, and the dynamic or private ports. You can actived this three range. Port number 0 is reserved and can't be used. see: http://en.wikipedia.org/wiki/Port_numbers """ __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value') _opt_type = 'port' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_args=None, properties=None, allow_range=False, allow_zero=False, allow_wellknown=True, allow_registred=True, allow_private=False): self._allow_range = allow_range self._min_value = None self._max_value = None ports_min = [0, 1, 1024, 49152] ports_max = [0, 1023, 49151, 65535] is_finally = False for index, allowed in enumerate([allow_zero, allow_wellknown, allow_registred, allow_private]): if self._min_value is None: if allowed: self._min_value = ports_min[index] elif not allowed: is_finally = True elif allowed and is_finally: raise ValueError(_('inconsistency in allowed range')) if allowed: self._max_value = ports_max[index] if self._max_value is None: raise ValueError(_('max value is empty')) super(PortOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_args=validator_args, properties=properties) def _validate(self, value): if self._allow_range and ":" in str(value): value = str(value).split(':') if len(value) != 2: raise ValueError('range must have two values only') if not value[0] < value[1]: raise ValueError('first port in range must be' ' smaller than the second one') else: value = [value] for val in value: if not self._min_value <= int(val) <= self._max_value: raise ValueError('port must be an between {0} and {1}' ''.format(self._min_value, self._max_value)) class NetworkOption(Option): "represents the choice of a network" __slots__ = tuple() _opt_type = 'network' def _validate(self, value): ip = IP(value) if ip.iptype() == 'RESERVED': raise ValueError(_("network mustn't not be in reserved class")) class NetmaskOption(Option): "represents the choice of a netmask" __slots__ = tuple() _opt_type = 'netmask' def _validate(self, value): IP('0.0.0.0/{0}'.format(value)) def _cons_network_netmask(self, optname, value, value_): #opts must be (netmask, network) options self.__cons_netmask(optname, value, value_, False) def _cons_ip_netmask(self, optname, value, value_): #opts must be (netmask, ip) options self.__cons_netmask(optname, value, value_, True) #def __cons_netmask(self, opt, value, context, index, opts, make_net): def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net): msg = None try: ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), make_net=make_net) #if cidr == 32, ip same has network if ip.prefixlen() != 32: try: IP('{0}/{1}'.format(val_ipnetwork, val_netmask), make_net=not make_net) except ValueError: if not make_net: msg = _("invalid network {0} ({1}) " "with netmask {2} ({3})," " this network is an IP") else: if make_net: msg = _("invalid IP {0} ({1}) with netmask {2} ({3})," " this IP is a network") except ValueError: if make_net: msg = _("invalid IP {0} ({1}) with netmask {2} ({3})") else: msg = _("invalid network {0} ({1}) with netmask {2} ({3})") if msg is not None: raise ValueError(msg.format(val_ipnetwork, optname, val_netmask, self._name)) class DomainnameOption(Option): "represents the choice of a domain name" __slots__ = ('_type', '_allow_ip') _opt_type = 'domainname' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_args=None, properties=None, allow_ip=False, type_='domainname'): #netbios: for MS domain #hostname: to identify the device #domainname: #fqdn: with tld, not supported yet if type_ not in ['netbios', 'hostname', 'domainname']: raise ValueError(_('unknown type_ {0} for hostname').format(type_)) self._type = type_ if allow_ip not in [True, False]: raise ValueError(_('allow_ip must be a boolean')) self._allow_ip = allow_ip super(DomainnameOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_args=validator_args, properties=properties) def _validate(self, value): if self._allow_ip is True: try: IP('{0}/32'.format(value)) return except ValueError: pass if self._type == 'netbios': length = 15 extrachar = '' elif self._type == 'hostname': length = 63 extrachar = '' elif self._type == 'domainname': length = 255 extrachar = '\.' if '.' not in value: raise ValueError(_("invalid value for {0}, must have dot" "").format(self._name)) if len(value) > length: raise ValueError(_("invalid domainname's length for" " {0} (max {1})").format(self._name, length)) if len(value) == 1: raise ValueError(_("invalid domainname's length for {0} (min 2)" "").format(self._name)) regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar) if re.match(regexp, value) is None: raise ValueError(_('invalid domainname')) class OptionDescription(_ReadOnlyOption): """Config's schema (organisation, group) and container of Options The `OptionsDescription` objects lives in the `tiramisu.config.Config`. """ __slots__ = ('_name', '_requires', '_cache_paths', '_group_type', '_properties', '_children', '_consistencies', '_calc_properties', '__weakref__', '_readonly', '_impl_informations') def __init__(self, name, doc, children, requires=None, properties=None): """ :param children: a list of options (including optiondescriptions) """ if not valid_name(name): raise ValueError(_("invalid name:" " {0} for optiondescription").format(name)) self._name = name self._impl_informations = {} self.impl_set_information('doc', doc) child_names = [child._name for child in children] #better performance like this valid_child = copy(child_names) valid_child.sort() old = None for child in valid_child: if child == old: raise ConflictError(_('duplicate option name: ' '{0}').format(child)) old = child self._children = (tuple(child_names), tuple(children)) self._calc_properties, self._requires = validate_requires_arg(requires, self._name) self._cache_paths = None self._consistencies = None if properties is None: properties = tuple() if not isinstance(properties, tuple): raise TypeError(_('invalid properties type {0} for {1},' ' must be a tuple').format(type(properties), self._name)) self._properties = properties # 'hidden', 'disabled'... # the group_type is useful for filtering OptionDescriptions in a config self._group_type = groups.default def impl_getdoc(self): return self.impl_get_information('doc') def __getattr__(self, name): if name in self.__slots__: return object.__getattribute__(self, name) try: return self._children[1][self._children[0].index(name)] except ValueError: raise AttributeError(_('unknown Option {0} ' 'in OptionDescription {1}' '').format(name, self._name)) def impl_getkey(self, config): return tuple([child.impl_getkey(getattr(config, child._name)) for child in self.impl_getchildren()]) def impl_getpaths(self, include_groups=False, _currpath=None): """returns a list of all paths in self, recursively _currpath should not be provided (helps with recursion) """ if _currpath is None: _currpath = [] paths = [] for option in self.impl_getchildren(): attr = option._name if isinstance(option, OptionDescription): if include_groups: paths.append('.'.join(_currpath + [attr])) paths += option.impl_getpaths(include_groups=include_groups, _currpath=_currpath + [attr]) else: paths.append('.'.join(_currpath + [attr])) return paths def impl_getchildren(self): return self._children[1] def impl_build_cache(self, cache_path=None, cache_option=None, _currpath=None, _consistencies=None): if _currpath is None and self._cache_paths is not None: # cache already set return if _currpath is None: save = True _currpath = [] _consistencies = {} else: save = False if cache_path is None: cache_path = [] cache_option = [] for option in self.impl_getchildren(): attr = option._name if attr.startswith('_cfgimpl'): continue if option in cache_option: raise ConflictError(_('duplicate option: {0}').format(option)) cache_option.append(option) option._readonly = True cache_path.append(str('.'.join(_currpath + [attr]))) if not isinstance(option, OptionDescription): if option._consistencies is not None: for consistency in option._consistencies: func, opt = consistency opts = (option, opt) _consistencies.setdefault(opt, []).append((func, opts)) _consistencies.setdefault(option, []).append((func, opts)) else: _currpath.append(attr) option.impl_build_cache(cache_path, cache_option, _currpath, _consistencies) _currpath.pop() if save: self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._consistencies = _consistencies self._readonly = True def impl_get_opt_by_path(self, path): try: return self._cache_paths[0][self._cache_paths[1].index(path)] except ValueError: raise AttributeError(_('no option for path {0}').format(path)) def impl_get_path_by_opt(self, opt): try: return self._cache_paths[1][self._cache_paths[0].index(opt)] except ValueError: raise AttributeError(_('no option {0} found').format(opt)) # ____________________________________________________________ def impl_set_group_type(self, group_type): """sets a given group object to an OptionDescription :param group_type: an instance of `GroupType` or `MasterGroupType` that lives in `setting.groups` """ if self._group_type != groups.default: raise TypeError(_('cannot change group_type if already set ' '(old {0}, new {1})').format(self._group_type, group_type)) if isinstance(group_type, groups.GroupType): self._group_type = group_type if isinstance(group_type, groups.MasterGroupType): #if master (same name has group) is set identical_master_child_name = False #for collect all slaves slaves = [] master = None for child in self.impl_getchildren(): if isinstance(child, OptionDescription): raise ValueError(_("master group {0} shall not have " "a subgroup").format(self._name)) if isinstance(child, SymLinkOption): raise ValueError(_("master group {0} shall not have " "a symlinkoption").format(self._name)) if not child.impl_is_multi(): raise ValueError(_("not allowed option {0} " "in group {1}" ": this option is not a multi" "").format(child._name, self._name)) if child._name == self._name: identical_master_child_name = True child._multitype = multitypes.master master = child else: slaves.append(child) if master is None: raise ValueError(_('master group with wrong' ' master name for {0}' ).format(self._name)) master._master_slaves = tuple(slaves) for child in self.impl_getchildren(): if child != master: child._master_slaves = master child._multitype = multitypes.slave if not identical_master_child_name: raise ValueError(_("no child has same nom has master group" " for: {0}").format(self._name)) else: raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) def impl_get_group_type(self): return self._group_type def _valid_consistency(self, opt, value, context=None, index=None): consistencies = self._consistencies.get(opt) if consistencies is not None: for consistency in consistencies: opt_ = consistency[1] ret = opt_[0]._launch_consistency(consistency[0], opt, value, context, index, opt_[1]) if ret is False: return False return True def validate_requires_arg(requires, name): """check malformed requirements and tranform dict to internal tuple :param requires: have a look at the :meth:`tiramisu.setting.Settings.apply_requires` method to know more about the description of the requires dictionary """ if requires is None: return None, None ret_requires = {} config_action = {} for require in requires: if not type(require) == dict: raise ValueError(_("malformed requirements type for option:" " {0}, must be a dict").format(name)) valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive', 'same_action') unknown_keys = frozenset(require.keys()) - frozenset(valid_keys) if unknown_keys != frozenset(): raise ValueError('malformed requirements for option: {0}' ' unknown keys {1}, must only ' '{2}'.format(name, unknown_keys, valid_keys)) try: option = require['option'] expected = require['expected'] action = require['action'] except KeyError: raise ValueError(_("malformed requirements for option: {0}" " require must have option, expected and" " action keys").format(name)) inverse = require.get('inverse', False) if inverse not in [True, False]: raise ValueError(_('malformed requirements for option: {0}' ' inverse must be boolean')) transitive = require.get('transitive', True) if transitive not in [True, False]: raise ValueError(_('malformed requirements for option: {0}' ' transitive must be boolean')) same_action = require.get('same_action', True) if same_action not in [True, False]: raise ValueError(_('malformed requirements for option: {0}' ' same_action must be boolean')) if not isinstance(option, Option): raise ValueError(_('malformed requirements ' 'must be an option in option {0}').format(name)) if option.impl_is_multi(): raise ValueError(_('malformed requirements option {0} ' 'should not be a multi').format(name)) if expected is not None: try: option._validate(expected) except ValueError as err: raise ValueError(_('malformed requirements second argument ' 'must be valid for option {0}' ': {1}').format(name, err)) if action in config_action: if inverse != config_action[action]: raise ValueError(_("inconsistency in action types" " for option: {0}" " action: {1}").format(name, action)) else: config_action[action] = inverse if action not in ret_requires: ret_requires[action] = {} if option not in ret_requires[action]: ret_requires[action][option] = (option, [expected], action, inverse, transitive, same_action) else: ret_requires[action][option][1].append(expected) ret = [] for opt_requires in ret_requires.values(): ret_action = [] for require in opt_requires.values(): req = (require[0], tuple(require[1]), require[2], require[3], require[4], require[5]) ret_action.append(req) ret.append(tuple(ret_action)) return frozenset(config_action.keys()), tuple(ret)