tiramisu/tiramisu/option/baseoption.py

683 lines
27 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2017-07-08 15:59:56 +02:00
# Copyright (C) 2014-2017 Team tiramisu (see AUTHORS for all contributors)
#
2013-09-22 22:33:09 +02:00
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
2013-09-22 22:33:09 +02:00
# 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 Lesser General Public License for more
# details.
#
2013-09-22 22:33:09 +02:00
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
2012-10-05 16:00:07 +02:00
# 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
# ____________________________________________________________
2013-02-25 15:52:10 +01:00
import re
2012-11-19 09:51:40 +01:00
from types import FunctionType
2017-11-13 22:45:53 +01:00
import weakref
2017-12-07 22:20:19 +01:00
from inspect import signature
2017-07-09 09:49:03 +02:00
2017-07-22 16:26:06 +02:00
from ..i18n import _
2017-07-24 20:39:01 +02:00
from ..setting import undefined
from ..error import ConfigError
2017-07-22 16:26:06 +02:00
2017-11-20 17:01:36 +01:00
STATIC_TUPLE = frozenset()
2014-07-06 15:31:57 +02:00
submulti = 2
2017-12-07 22:20:19 +01:00
NAME_REGEXP = re.compile(r'^[a-z][a-zA-Z\d_-]*$')
2017-07-22 16:26:06 +02:00
FORBIDDEN_NAMES = frozenset(['iter_all', 'iter_group', 'find', 'find_first',
2015-12-18 22:44:46 +01:00
'make_dict', 'unwrap_from_path', 'read_only',
'read_write', 'getowner', 'set_contexts'])
2013-02-25 15:52:10 +01:00
2013-04-03 12:20:26 +02:00
2013-02-25 15:52:10 +01:00
def valid_name(name):
2017-07-23 18:14:34 +02:00
"""an option's name is a str and does not start with 'impl' or 'cfgimpl'
and name is not a function name"""
if not isinstance(name, str):
2013-04-14 12:01:32 +02:00
return False
2017-07-23 18:14:34 +02:00
return re.match(NAME_REGEXP, name) is not None and \
2017-07-22 16:26:06 +02:00
name not in FORBIDDEN_NAMES and \
2015-12-18 22:44:46 +01:00
not name.startswith('impl_') and \
2017-07-23 18:14:34 +02:00
not name.startswith('cfgimpl_')
2017-12-19 23:11:45 +01:00
def validate_calculator(callback,
callback_params,
type_,
callbackoption):
2017-07-23 18:14:34 +02:00
"""validate function and parameter set for callback, validation, ...
"""
2017-12-19 23:11:45 +01:00
2017-07-23 18:14:34 +02:00
def _validate_option(option):
#validate option
2017-12-19 23:11:45 +01:00
if not isinstance(option, OnlyOption):
raise ValueError(_('{}_params must have an option'
' not a {} for first argument'
).format(type_, type(option)))
2017-12-13 22:15:34 +01:00
if option.impl_is_symlinkoption():
cur_opt = option.impl_getopt()
2017-07-23 18:14:34 +02:00
else:
2017-12-13 22:15:34 +01:00
cur_opt = option
2017-07-23 18:14:34 +02:00
if cur_opt != callbackoption:
2017-09-17 15:55:32 +02:00
cur_opt._add_dependency(callbackoption)
callbackoption._has_dependency = True
2017-07-23 18:14:34 +02:00
def _validate_force_permissive(force_permissive):
#validate force_permissive
if not isinstance(force_permissive, bool):
raise ValueError(_('{}_params must have a boolean'
' not a {} for second argument'
2017-12-07 22:20:19 +01:00
).format(type_,
type(force_permissive)))
2017-07-23 18:14:34 +02:00
2017-12-19 23:11:45 +01:00
def _validate_calculator(callbk):
2017-07-23 18:14:34 +02:00
if isinstance(callbk, tuple):
if len(callbk) == 1:
if callbk not in ((None,), ('index',)):
raise ValueError(_('{0}_params with length of '
'tuple as 1 must only have '
'None as first value').format(type_))
2017-09-17 15:55:32 +02:00
if callbk == ((None,)):
callbackoption._has_calc_context = True
2017-07-23 18:14:34 +02:00
return
elif len(callbk) != 2:
raise ValueError(_('{0}_params must only have 1 or 2 '
'as length').format(type_))
option, force_permissive = callbk
_validate_option(option)
_validate_force_permissive(force_permissive)
if not isinstance(callback, FunctionType):
raise ValueError(_('{0} must be a function').format(type_))
if callback_params is not None:
if not isinstance(callback_params, dict):
raise ValueError(_('{0}_params must be a dict').format(type_))
for key, callbacks in callback_params.items():
if key != '' and len(callbacks) != 1:
raise ValueError(_("{0}_params with key {1} mustn't have "
"length different to 1").format(type_,
key))
if not isinstance(callbacks, tuple):
raise ValueError(_('{0}_params must be tuple for key "{1}"'
).format(type_, key))
for callbk in callbacks:
2017-12-19 23:11:45 +01:00
_validate_calculator(callbk)
2013-02-25 16:06:10 +01:00
2013-04-03 12:20:26 +02:00
2017-07-23 18:14:34 +02:00
#____________________________________________________________
#
2017-07-22 16:26:06 +02:00
class Base(object):
2017-07-24 18:27:24 +02:00
"""Base use by all *Option* classes (Option, OptionDescription, SymLinkOption, ...)
"""
2017-07-22 16:26:06 +02:00
__slots__ = ('_name',
'_informations',
#calcul
'_subdyn',
'_requires',
'_properties',
'_calc_properties',
#
'_consistencies',
#other
'_has_dependency',
'_dependencies',
2017-09-17 15:55:32 +02:00
'_has_calc_context',
2017-07-22 16:26:06 +02:00
'__weakref__'
)
2017-12-07 22:20:19 +01:00
def __init__(self,
name,
doc,
requires=None,
properties=None,
is_multi=False):
if not valid_name(name):
2017-11-12 14:33:05 +01:00
raise ValueError(_('invalid name: "{0}" for option').format(name))
2015-12-22 22:06:14 +01:00
if requires is not None:
2017-12-07 22:20:19 +01:00
calc_properties, requires = validate_requires_arg(self,
is_multi,
requires,
name)
2015-12-22 22:06:14 +01:00
else:
calc_properties = frozenset()
requires = undefined
if properties is None:
2017-12-29 11:38:41 +01:00
properties = frozenset()
if isinstance(properties, tuple):
properties = frozenset(properties)
2017-11-20 17:01:36 +01:00
if is_multi and 'empty' not in properties:
2017-12-29 11:38:41 +01:00
properties = properties | {'empty'}
if not isinstance(properties, frozenset):
raise TypeError(_('invalid properties type {0} for {1},'
2017-12-29 11:38:41 +01:00
' must be a frozenset').format(type(properties),
2017-12-07 22:20:19 +01:00
name))
2017-12-29 11:38:41 +01:00
if calc_properties != frozenset([]) and properties:
set_forbidden_properties = calc_properties & properties
if set_forbidden_properties != frozenset():
2017-12-29 11:38:41 +01:00
raise ValueError(_('conflict: properties already set in '
'requirement {0}').format(list(set_forbidden_properties)))
2017-07-22 16:26:06 +02:00
_setattr = object.__setattr__
_setattr(self, '_name', name)
_setattr(self, '_informations', {'doc': doc})
if calc_properties is not undefined:
_setattr(self, '_calc_properties', calc_properties)
if requires is not undefined:
_setattr(self, '_requires', requires)
2017-11-13 22:45:53 +01:00
if properties:
2017-07-22 16:26:06 +02:00
_setattr(self, '_properties', properties)
2014-10-25 22:11:31 +02:00
2017-12-19 23:11:45 +01:00
def _get_function_args(self,
function):
args = set()
kwargs = set()
positional = False
keyword = False
for param in signature(function).parameters.values():
if param.kind == param.VAR_POSITIONAL:
positional = True
elif param.kind == param.VAR_KEYWORD:
keyword = True
elif param.default is param.empty:
args.add(param.name)
else:
kwargs.add(param.name)
return args, kwargs, positional, keyword
def _get_parameters_args(self,
calculator_params,
add_value):
args = set()
kwargs = set()
if add_value:
args.add('value')
2017-12-27 15:48:49 +01:00
if self.impl_is_dynoptiondescription():
kwargs.add('suffix')
if calculator_params is not None:
for param in calculator_params.keys():
if param == '':
for idx, _ in enumerate(calculator_params['']):
# construct an appropriate name
args.add('param{}'.format(idx))
else:
kwargs.add(param)
2017-12-19 23:11:45 +01:00
return args, kwargs
def _build_calculator_params(self,
calculator,
calculator_params,
add_value=False):
2017-12-27 15:48:49 +01:00
is_multi = self.impl_is_optiondescription() or self.impl_is_multi()
if calculator_params is None:
calculator_params = {}
func_args, func_kwargs, func_positional, func_keyword = self._get_function_args(calculator)
calculator_args, calculator_kwargs = self._get_parameters_args(calculator_params, add_value)
# remove knowned kwargs
common_kwargs = func_kwargs & calculator_kwargs
func_kwargs -= common_kwargs
calculator_kwargs -= common_kwargs
# remove knowned calculator's kwargs in func's args
common = func_args & calculator_kwargs
func_args -= common
calculator_kwargs -= common
# remove unknown calculator's args in func's args
for idx in range(min(len(calculator_args), len(func_args))):
func_args.pop()
calculator_args.pop()
# remove unknown calculator's args in func's kwargs
if is_multi:
2017-12-19 23:11:45 +01:00
func_kwargs_left = func_kwargs - {'index', 'self'}
2017-12-27 15:48:49 +01:00
else:
func_kwargs_left = func_kwargs
func_kwargs_pop = set()
for idx in range(min(len(calculator_args), len(func_kwargs_left))):
func_kwargs_pop.add(func_kwargs_left.pop())
calculator_args.pop()
func_kwargs -= func_kwargs_pop
if func_positional:
calculator_args = set()
if func_keyword:
calculator_kwargs = set()
if calculator_args or calculator_kwargs:
# there is more args/kwargs than expected!
raise ConfigError(_('cannot find those arguments "{}" in function "{}" for "{}"'
'').format(list(calculator_args | calculator_kwargs),
calculator.__name__,
self.impl_get_display_name()))
has_self = False
has_index = False
if is_multi and func_args:
# there is extra args/kwargs
if not self.impl_is_optiondescription() and is_multi:
params = list(calculator_params.get('', tuple()))
if add_value:
# only for validator
has_self = True
params.append((self, False))
func_args.pop()
if func_args:
has_index = True
params.append(('index',))
func_args.pop()
if func_args:
raise ConfigError(_('missing those arguements "{}" in function "{}" for "{}"'
'').format(list(func_args),
2017-12-19 23:11:45 +01:00
calculator.__name__,
self.impl_get_display_name()))
2017-12-27 15:48:49 +01:00
calculator_params[''] = tuple(params)
if not self.impl_is_optiondescription() and self.impl_is_multi():
if add_value and not has_self and 'self' in func_kwargs:
# only for validator
calculator_params['self'] = (self, False)
if not has_index and 'index' in func_kwargs:
calculator_params['index'] = (('index',),)
2017-12-19 23:11:45 +01:00
return calculator_params
2017-12-07 22:20:19 +01:00
def impl_has_dependency(self,
self_is_dep=True):
2017-09-17 15:55:32 +02:00
if self_is_dep is True:
if self.impl_is_master_slaves():
return True
return getattr(self, '_has_dependency', False)
else:
return hasattr(self, '_dependencies')
2017-12-07 22:20:19 +01:00
def _get_dependencies(self,
context):
2017-09-17 15:55:32 +02:00
if context:
od = context.cfgimpl_get_description()
2017-11-13 22:45:53 +01:00
ret = set(getattr(self, '_dependencies', STATIC_TUPLE))
2017-09-17 15:55:32 +02:00
if context and hasattr(od, '_dependencies'):
2017-11-13 22:45:53 +01:00
return set(od._dependencies) | ret
2017-09-17 15:55:32 +02:00
else:
2017-11-13 22:45:53 +01:00
return ret
2016-10-16 21:37:55 +02:00
2017-12-07 22:20:19 +01:00
def _add_dependency(self,
option):
2017-11-13 22:45:53 +01:00
options = self._get_dependencies(None)
options.add(weakref.ref(option))
2017-09-17 15:55:32 +02:00
self._dependencies = tuple(options)
2016-10-16 21:37:55 +02:00
2017-12-07 22:20:19 +01:00
def impl_set_callback(self,
callback,
callback_params=None,
_init=False):
if callback is None and callback_params is not None:
2017-07-24 18:27:24 +02:00
raise ValueError(_("params defined for a callback function but "
"no callback defined"
2017-11-28 22:42:30 +01:00
' yet for option "{0}"').format(
2017-07-24 18:27:24 +02:00
self.impl_getname()))
2015-12-18 22:44:46 +01:00
if not _init and self.impl_get_callback()[0] is not None:
2016-11-19 19:16:31 +01:00
raise ConfigError(_("a callback is already set for {0}, "
"cannot set another one's").format(self.impl_getname()))
2017-12-19 23:11:45 +01:00
self._validate_calculator(callback,
callback_params)
2014-10-25 22:11:31 +02:00
if callback is not None:
2017-12-19 23:11:45 +01:00
validate_calculator(callback,
callback_params,
'callback',
self)
callback_params = self._build_calculator_params(callback,
callback_params)
2017-07-22 16:26:06 +02:00
val = getattr(self, '_val_call', (None,))[0]
if callback_params is None or callback_params == {}:
val_call = (callback,)
else:
val_call = tuple([callback, callback_params])
self._val_call = (val, val_call)
2014-06-19 23:22:39 +02:00
def impl_is_optiondescription(self):
2017-10-14 13:33:25 +02:00
#FIXME deplacer dans OpenDescription ?
2014-06-19 23:22:39 +02:00
return self.__class__.__name__ in ['OptionDescription',
'DynOptionDescription',
2017-10-14 13:33:25 +02:00
'SynDynOptionDescription',
'MasterSlaves']
2014-06-19 23:22:39 +02:00
def impl_is_dynoptiondescription(self):
return self.__class__.__name__ in ['DynOptionDescription',
'SynDynOptionDescription']
2017-07-22 16:26:06 +02:00
def impl_getname(self):
return self._name
def impl_is_readonly(self):
return not isinstance(getattr(self, '_informations', dict()), dict)
def impl_getproperties(self):
2017-12-30 18:31:56 +01:00
return getattr(self, '_properties', frozenset())
2017-07-22 16:26:06 +02:00
2017-09-17 15:55:32 +02:00
def _set_readonly(self):
2017-07-22 16:26:06 +02:00
if not self.impl_is_readonly():
_setattr = object.__setattr__
dico = self._informations
keys = tuple(dico.keys())
if len(keys) == 1:
dico = dico['doc']
else:
dico = tuple([keys, tuple(dico.values())])
_setattr(self, '_informations', dico)
2017-09-17 15:55:32 +02:00
extra = getattr(self, '_extra', None)
if extra is not None:
_setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())]))
2017-07-22 16:26:06 +02:00
2017-12-07 22:20:19 +01:00
def _impl_setsubdyn(self,
subdyn):
2017-11-13 22:45:53 +01:00
self._subdyn = weakref.ref(subdyn)
2017-07-22 16:26:06 +02:00
def impl_getrequires(self):
2017-07-23 18:14:34 +02:00
return getattr(self, '_requires', STATIC_TUPLE)
2017-07-22 16:26:06 +02:00
def impl_get_callback(self):
call = getattr(self, '_val_call', (None, None))[1]
if call is None:
ret_call = (None, {})
elif len(call) == 1:
ret_call = (call[0], {})
else:
ret_call = call
return ret_call
# ____________________________________________________________
# information
2017-12-07 22:20:19 +01:00
def impl_get_information(self,
key,
default=undefined):
2017-07-22 16:26:06 +02:00
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
def _is_string(infos):
2017-12-07 22:20:19 +01:00
return isinstance(infos, str)
2017-07-22 16:26:06 +02:00
dico = self._informations
if isinstance(dico, tuple):
if key in dico[0]:
return dico[1][dico[0].index(key)]
elif _is_string(dico):
if key == 'doc':
return dico
elif isinstance(dico, dict):
if key in dico:
return dico[key]
if default is not undefined:
return default
raise ValueError(_("information's item not found: {0}").format(
key))
2017-12-07 22:20:19 +01:00
def impl_set_information(self,
key,
value):
2017-07-22 16:26:06 +02:00
"""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")
"""
if self.impl_is_readonly():
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
2017-12-07 22:20:19 +01:00
" read-only").format(self.__class__.__name__,
self,
#self.impl_getname(),
key))
2017-07-22 16:26:06 +02:00
self._informations[key] = value
2013-09-02 15:06:55 +02:00
class BaseOption(Base):
"""This abstract base class stands for attribute access
in options that have to be set only once, it is of course done in the
__setattr__ method
"""
__slots__ = tuple()
2017-07-21 18:46:11 +02:00
def __getstate__(self):
raise NotImplementedError()
2017-12-07 22:20:19 +01:00
def __setattr__(self,
name,
value):
"""set once and only once some attributes in the option,
like `_name`. `_name` cannot be changed one the option and
pushed in the :class:`tiramisu.option.OptionDescription`.
if the attribute `_readonly` is set to `True`, the option is
"frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property)
"""
if name != '_option' and \
2017-07-21 18:46:11 +02:00
not isinstance(value, tuple):
is_readonly = False
2014-11-10 09:13:44 +01:00
# never change _name dans _opt
if name == '_name':
2017-07-21 22:34:41 +02:00
if self.impl_getname() is not None:
#so _name is already set
is_readonly = True
elif name != '_readonly':
is_readonly = self.impl_is_readonly()
if is_readonly:
2017-12-23 10:40:41 +01:00
raise AttributeError(_('"{}" ({}) object attribute "{}" is'
' read-only').format(self.__class__.__name__,
self.impl_get_display_name(),
2017-12-07 22:20:19 +01:00
name))
super(BaseOption, self).__setattr__(name, value)
2017-12-07 22:20:19 +01:00
def impl_getpath(self,
context):
2014-06-19 23:22:39 +02:00
return context.cfgimpl_get_description().impl_get_path_by_opt(self)
2014-07-06 15:31:57 +02:00
def impl_has_callback(self):
"to know if a callback has been defined or not"
2014-10-25 22:11:31 +02:00
return self.impl_get_callback()[0] is not None
2014-07-06 15:31:57 +02:00
def _is_subdyn(self):
return getattr(self, '_subdyn', None) is not None
2014-07-06 15:31:57 +02:00
2017-12-07 22:20:19 +01:00
def _impl_valid_string(self,
value):
if not isinstance(value, str):
2017-12-13 22:15:34 +01:00
raise ValueError(_('invalid string'))
2017-12-07 22:20:19 +01:00
def impl_get_display_name(self,
dyn_name=None):
2016-10-23 23:18:06 +02:00
name = self.impl_getdoc()
if name is None or name == '':
if dyn_name is not None:
name = dyn_name
else:
name = self.impl_getname()
2016-10-23 23:18:06 +02:00
return name
2017-11-20 17:01:36 +01:00
def reset_cache(self,
opt,
path,
obj,
type_,
resetted_opts):
if opt in resetted_opts:
return
if not type_ == 'values' or not opt.impl_is_optiondescription():
if type_ != 'permissives':
obj._p_.delcache(path)
if type_ in ['settings', 'permissives']:
obj._pp_.delcache(path)
2017-12-19 23:11:45 +01:00
resetted_opts.append(opt)
2017-07-08 15:59:56 +02:00
2017-11-23 16:56:14 +01:00
def impl_is_symlinkoption(self):
return False
2017-07-22 16:26:06 +02:00
2017-07-24 20:39:01 +02:00
class OnlyOption(BaseOption):
__slots__ = tuple()
2017-07-22 16:26:06 +02:00
2012-10-05 16:00:07 +02:00
2017-12-07 22:20:19 +01:00
def validate_requires_arg(new_option,
multi,
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
"""
2017-05-20 16:28:19 +02:00
def get_option(require):
option = require['option']
2017-11-23 16:56:14 +01:00
#FIXME etrange ...
if not hasattr(option, 'impl_is_symlinkoption'):
2017-05-20 16:28:19 +02:00
raise ValueError(_('malformed requirements '
'must be an option in option {0}').format(name))
if not multi and option.impl_is_multi():
raise ValueError(_('malformed requirements '
'multi option must not set '
'as requires of non multi option {0}').format(name))
2017-09-17 15:55:32 +02:00
option._add_dependency(new_option)
2017-05-20 16:28:19 +02:00
return option
2017-12-07 22:20:19 +01:00
def _set_expected(action,
inverse,
transitive,
same_action,
option,
expected,
operator):
2017-05-20 16:28:19 +02:00
if inverse not in ret_requires[action]:
ret_requires[action][inverse] = ([(option, [expected])], action, inverse, transitive, same_action, operator)
else:
for exp in ret_requires[action][inverse][0]:
if exp[0] == option:
exp[1].append(expected)
break
else:
ret_requires[action][inverse][0].append((option, [expected]))
2017-12-07 22:20:19 +01:00
def set_expected(require,
ret_requires):
2017-05-20 16:28:19 +02:00
expected = require['expected']
inverse = get_inverse(require)
transitive = get_transitive(require)
same_action = get_sameaction(require)
operator = get_operator(require)
if isinstance(expected, list):
for exp in expected:
2017-07-09 09:49:03 +02:00
if set(exp.keys()) != {'option', 'value'}:
2017-05-20 16:28:19 +02:00
raise ValueError(_('malformed requirements expected must have '
'option and value for option {0}').format(name))
option = exp['option']
2017-09-17 15:55:32 +02:00
option._add_dependency(new_option)
2017-05-20 16:28:19 +02:00
if option is not None:
2017-12-13 22:15:34 +01:00
err = option._validate(exp['value'], undefined)
2017-05-20 16:28:19 +02:00
if err:
raise ValueError(_('malformed requirements expected value '
'must be valid for option {0}'
': {1}').format(name, err))
2017-12-07 22:20:19 +01:00
_set_expected(action,
inverse,
transitive,
same_action,
option,
exp['value'],
operator)
2017-05-20 16:28:19 +02:00
else:
option = get_option(require)
if expected is not None:
2017-12-13 22:15:34 +01:00
err = option._validate(expected, undefined)
2017-05-20 16:28:19 +02:00
if err:
raise ValueError(_('malformed requirements expected value '
'must be valid for option {0}'
': {1}').format(name, err))
2017-12-07 22:20:19 +01:00
_set_expected(action,
inverse,
transitive,
same_action,
option,
expected,
operator)
2017-05-20 16:28:19 +02:00
def get_action(require):
action = require['action']
if action == 'force_store_value':
raise ValueError(_("malformed requirements for option: {0}"
" action cannot be force_store_value"
).format(name))
return action
def get_inverse(require):
inverse = require.get('inverse', False)
if inverse not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' inverse must be boolean'))
return inverse
def get_transitive(require):
transitive = require.get('transitive', True)
if transitive not in [True, False]:
raise ValueError(_('malformed requirements for option: {0}'
' transitive must be boolean'))
return transitive
def get_sameaction(require):
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'))
return same_action
def get_operator(require):
operator = require.get('operator', 'or')
if operator not in ['and', 'or']:
2017-07-21 22:34:41 +02:00
raise ValueError(_('malformed requirements for option: "{0}"'
' operator must be "or" or "and"').format(operator))
2017-05-20 16:28:19 +02:00
return operator
ret_requires = {}
config_action = set()
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires:
if not isinstance(require, dict):
raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
2017-05-20 16:28:19 +02:00
'same_action', 'operator')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset():
2014-06-19 23:22:39 +02:00
raise ValueError(_('malformed requirements for option: {0}'
' unknown keys {1}, must only '
2014-06-19 23:22:39 +02:00
'{2}').format(name,
unknown_keys,
valid_keys))
# prepare all attributes
2017-05-20 16:28:19 +02:00
if not ('expected' in require and isinstance(require['expected'], list)) and \
not ('option' in require and 'expected' in require) or \
2015-12-26 10:57:20 +01:00
'action' not in require:
raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and"
" action keys").format(name))
2017-05-20 16:28:19 +02:00
action = get_action(require)
config_action.add(action)
if action not in ret_requires:
ret_requires[action] = {}
2017-05-20 16:28:19 +02:00
set_expected(require, ret_requires)
# transform dict to tuple
ret = []
2017-05-20 16:28:19 +02:00
for requires in ret_requires.values():
ret_action = []
2017-05-20 16:28:19 +02:00
for require in requires.values():
ret_action.append((tuple(require[0]), require[1],
require[2], require[3], require[4], require[5]))
ret.append(tuple(ret_action))
return frozenset(config_action), tuple(ret)