Merge branch 'master' into lgpl
This commit is contained in:
@ -40,6 +40,13 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
:param max_len: max length for a multi
|
||||
:type max_len: int
|
||||
|
||||
The callback_params is a dict. Key is used to build args (if key is '')
|
||||
and kwargs (otherwise). Values are tuple of:
|
||||
- values
|
||||
- tuple with option and boolean's force_permissive (True when don't raise
|
||||
if PropertiesOptionError)
|
||||
Values could have multiple values only when key is ''.
|
||||
|
||||
* if no callback_params:
|
||||
=> calculate()
|
||||
|
||||
@ -91,7 +98,6 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
- a multi option with an other multi option but with same length
|
||||
opt1 == [1, 2, 3]
|
||||
opt2 == [11, 12, 13]
|
||||
callback_params={'': ((opt1, False), (opt2, False))}
|
||||
=> calculate(1, 11)
|
||||
=> calculate(2, 12)
|
||||
=> calculate(3, 13)
|
||||
@ -99,9 +105,13 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
- a multi option with an other multi option but with different length
|
||||
opt1 == [1, 2, 3]
|
||||
opt2 == [11, 12]
|
||||
callback_params={'': ((opt1, False), (opt2, False))}
|
||||
=> ConfigError()
|
||||
|
||||
- a multi option without value with a simple option
|
||||
opt1 == []
|
||||
opt2 == 11
|
||||
=> []
|
||||
|
||||
* if callback_params={'value': ((opt1, False), (opt2, False))}
|
||||
=> ConfigError()
|
||||
|
||||
@ -113,39 +123,47 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
If calculate return list, this list is extend to return value.
|
||||
"""
|
||||
tcparams = {}
|
||||
# if callback_params has a callback, launch several time calculate()
|
||||
one_is_multi = False
|
||||
len_multi = 0
|
||||
# multi's option should have same value for all option
|
||||
len_multi = None
|
||||
|
||||
for key, callbacks in callback_params.items():
|
||||
for callbk in callbacks:
|
||||
if isinstance(callbk, tuple):
|
||||
# callbk is something link (opt, True|False)
|
||||
option, force_permissive = callbk
|
||||
path = config.cfgimpl_get_description().impl_get_path_by_opt(
|
||||
option)
|
||||
# get value
|
||||
try:
|
||||
path = config.cfgimpl_get_description().impl_get_path_by_opt(option)
|
||||
value = config._getattr(path, force_permissive=True)
|
||||
except PropertiesOptionError as err:
|
||||
if force_permissive:
|
||||
continue
|
||||
raise ConfigError(_('unable to carry out a calculation, '
|
||||
'option {0} has properties: {1} for: '
|
||||
'{2}').format(option._name, err.proptype,
|
||||
'{2}').format(option._name,
|
||||
err.proptype,
|
||||
name))
|
||||
is_multi = option.impl_is_multi()
|
||||
if is_multi:
|
||||
if value is not None:
|
||||
len_value = len(value)
|
||||
if len_multi != 0 and len_multi != len_value:
|
||||
raise ConfigError(_('unable to carry out a '
|
||||
'calculation, option value with'
|
||||
' multi types must have same '
|
||||
'length for: {0}').format(name))
|
||||
len_multi = len_value
|
||||
len_value = len(value)
|
||||
if len_multi is not None and len_multi != len_value:
|
||||
raise ConfigError(_('unable to carry out a '
|
||||
'calculation, option value with'
|
||||
' multi types must have same '
|
||||
'length for: {0}').format(name))
|
||||
len_multi = len_value
|
||||
one_is_multi = True
|
||||
tcparams.setdefault(key, []).append((value, is_multi))
|
||||
else:
|
||||
# callbk is a value and not a multi
|
||||
tcparams.setdefault(key, []).append((callbk, False))
|
||||
|
||||
# if one value is a multi, launch several time calculate
|
||||
# if index is set, return a value
|
||||
# if no index, return a list
|
||||
if one_is_multi:
|
||||
ret = []
|
||||
if index:
|
||||
@ -160,19 +178,20 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
else:
|
||||
range_ = range(len_multi)
|
||||
for incr in range_:
|
||||
tcp = {}
|
||||
params = []
|
||||
args = []
|
||||
kwargs = {}
|
||||
for key, couples in tcparams.items():
|
||||
for couple in couples:
|
||||
value, ismulti = couple
|
||||
if ismulti and value is not None:
|
||||
if key == '':
|
||||
params.append(value[incr])
|
||||
else:
|
||||
tcp[key] = value[incr]
|
||||
if ismulti:
|
||||
val = value[incr]
|
||||
else:
|
||||
params.append(value)
|
||||
calc = calculate(name, callback, params, tcp)
|
||||
val = value
|
||||
if key == '':
|
||||
args.append(val)
|
||||
else:
|
||||
kwargs[key] = val
|
||||
calc = calculate(callback, args, kwargs)
|
||||
if index:
|
||||
ret = calc
|
||||
else:
|
||||
@ -182,24 +201,26 @@ def carry_out_calculation(name, config, callback, callback_params,
|
||||
ret.append(calc)
|
||||
return ret
|
||||
else:
|
||||
tcp = {}
|
||||
params = []
|
||||
# no value is multi
|
||||
# return a single value
|
||||
args = []
|
||||
kwargs = {}
|
||||
for key, couples in tcparams.items():
|
||||
for couple in couples:
|
||||
# couple[1] (ismulti) is always False
|
||||
if key == '':
|
||||
value = couple[0]
|
||||
params.append(value)
|
||||
args.append(couple[0])
|
||||
else:
|
||||
tcp[key] = couple[0]
|
||||
return calculate(name, callback, params, tcp)
|
||||
kwargs[key] = couple[0]
|
||||
return calculate(callback, args, kwargs)
|
||||
|
||||
|
||||
def calculate(name, callback, params, tcparams):
|
||||
def calculate(callback, args, kwargs):
|
||||
"""wrapper that launches the 'callback'
|
||||
|
||||
:param callback: callback name
|
||||
:param params: in the callback's arity, the unnamed parameters
|
||||
:param tcparams: in the callback's arity, the named parameters
|
||||
:param callback: callback function
|
||||
:param args: in the callback's arity, the unnamed parameters
|
||||
:param kwargs: in the callback's arity, the named parameters
|
||||
|
||||
"""
|
||||
return callback(*params, **tcparams)
|
||||
return callback(*args, **kwargs)
|
||||
|
@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"options handler global entry point"
|
||||
# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
@ -19,6 +18,7 @@
|
||||
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
|
||||
# the whole pypy projet is under MIT licence
|
||||
# ____________________________________________________________
|
||||
"options handler global entry point"
|
||||
import weakref
|
||||
from tiramisu.error import PropertiesOptionError, ConfigError
|
||||
from tiramisu.option import OptionDescription, Option, SymLinkOption
|
||||
@ -30,7 +30,11 @@ from tiramisu.i18n import _
|
||||
|
||||
|
||||
class SubConfig(object):
|
||||
"sub configuration management entry"
|
||||
"""Sub configuration management entry.
|
||||
Tree if OptionDescription's responsability. SubConfig are generated
|
||||
on-demand. A Config is also a SubConfig.
|
||||
Root Config is call context below
|
||||
"""
|
||||
__slots__ = ('_impl_context', '_impl_descr', '_impl_path')
|
||||
|
||||
def __init__(self, descr, context, subpath=None):
|
||||
@ -55,6 +59,7 @@ class SubConfig(object):
|
||||
|
||||
def cfgimpl_reset_cache(self, only_expired=False, only=('values',
|
||||
'settings')):
|
||||
"remove cache (in context)"
|
||||
self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
|
||||
|
||||
def cfgimpl_get_home_by_path(self, path, force_permissive=False,
|
||||
@ -179,7 +184,9 @@ class SubConfig(object):
|
||||
homeconfig, name = self.cfgimpl_get_home_by_path(name)
|
||||
return homeconfig.__setattr__(name, value)
|
||||
child = getattr(self.cfgimpl_get_description(), name)
|
||||
if not isinstance(child, SymLinkOption):
|
||||
if isinstance(child, OptionDescription):
|
||||
raise TypeError(_("can't assign to an OptionDescription"))
|
||||
elif not isinstance(child, SymLinkOption):
|
||||
if self._impl_path is None:
|
||||
path = name
|
||||
else:
|
||||
|
@ -56,3 +56,35 @@ class SlaveError(Exception):
|
||||
class ConstError(TypeError):
|
||||
"no uniq value in _NameSpace"
|
||||
pass
|
||||
|
||||
|
||||
#Warning
|
||||
class ValueWarning(UserWarning):
|
||||
"""Option could warn user and not raise ValueError.
|
||||
|
||||
Example:
|
||||
|
||||
>>> import warnings
|
||||
>>> from tiramisu.error import ValueWarning
|
||||
>>> from tiramisu.option import StrOption, OptionDescription
|
||||
>>> from tiramisu.config import Config
|
||||
>>> warnings.simplefilter("always", ValueWarning)
|
||||
>>> def a(val):
|
||||
... raise ValueError('pouet')
|
||||
...
|
||||
>>> s=StrOption('s', '', validator=a, warnings_only=True)
|
||||
>>> o=OptionDescription('o', '', [s])
|
||||
>>> c=Config(o)
|
||||
>>> c.s = 'val'
|
||||
StrOption:0: ValueWarning: invalid value val for option s: pouet
|
||||
>>> with warnings.catch_warnings(record=True) as w:
|
||||
... c.s = 'val'
|
||||
...
|
||||
>>> w[0].message.opt == s
|
||||
True
|
||||
>>> print str(w[0].message)
|
||||
invalid value val for option s: pouet
|
||||
"""
|
||||
def __init__(self, msg, opt):
|
||||
self.opt = opt
|
||||
super(ValueWarning, self).__init__(msg)
|
||||
|
@ -24,8 +24,9 @@ import sys
|
||||
from copy import copy, deepcopy
|
||||
from types import FunctionType
|
||||
from IPy import IP
|
||||
import warnings
|
||||
|
||||
from tiramisu.error import ConflictError, ConfigError
|
||||
from tiramisu.error import ConfigError, ConflictError, ValueWarning
|
||||
from tiramisu.setting import groups, multitypes
|
||||
from tiramisu.i18n import _
|
||||
from tiramisu.autolib import carry_out_calculation
|
||||
@ -59,9 +60,8 @@ class BaseOption(object):
|
||||
__setattr__ method
|
||||
"""
|
||||
__slots__ = ('_name', '_requires', '_properties', '_readonly',
|
||||
'_consistencies', '_calc_properties', '_impl_informations',
|
||||
'_state_consistencies', '_state_readonly', '_state_requires',
|
||||
'_stated')
|
||||
'_calc_properties', '_impl_informations',
|
||||
'_state_readonly', '_state_requires', '_stated')
|
||||
|
||||
def __init__(self, name, doc, requires, properties):
|
||||
if not valid_name(name):
|
||||
@ -71,7 +71,6 @@ class BaseOption(object):
|
||||
self.impl_set_information('doc', doc)
|
||||
self._calc_properties, self._requires = validate_requires_arg(
|
||||
requires, self._name)
|
||||
self._consistencies = None
|
||||
if properties is None:
|
||||
properties = tuple()
|
||||
if not isinstance(properties, tuple):
|
||||
@ -96,8 +95,7 @@ class BaseOption(object):
|
||||
"frozen" (which has noting to do with the high level "freeze"
|
||||
propertie or "read_only" property)
|
||||
"""
|
||||
if not name.startswith('_state') and name not in ('_cache_paths',
|
||||
'_consistencies'):
|
||||
if not name.startswith('_state') and not name.startswith('_cache'):
|
||||
is_readonly = False
|
||||
# never change _name
|
||||
if name == '_name':
|
||||
@ -107,15 +105,12 @@ class BaseOption(object):
|
||||
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
|
||||
elif name != '_readonly':
|
||||
try:
|
||||
if self._readonly is True:
|
||||
is_readonly = True
|
||||
except AttributeError:
|
||||
self._readonly = False
|
||||
if is_readonly:
|
||||
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
|
||||
" read-only").format(
|
||||
@ -147,58 +142,6 @@ class BaseOption(object):
|
||||
raise ValueError(_("information's item not found: {0}").format(
|
||||
key))
|
||||
|
||||
# serialize/unserialize
|
||||
def _impl_convert_consistencies(self, descr, load=False):
|
||||
"""during serialization process, many things have to be done.
|
||||
one of them is the localisation of the options.
|
||||
The paths are set once for all.
|
||||
|
||||
:type descr: :class:`tiramisu.option.OptionDescription`
|
||||
:param load: `True` if we are at the init of the option description
|
||||
:type load: bool
|
||||
"""
|
||||
if not load and self._consistencies is None:
|
||||
self._state_consistencies = None
|
||||
elif load and self._state_consistencies is None:
|
||||
self._consistencies = None
|
||||
del(self._state_consistencies)
|
||||
else:
|
||||
if load:
|
||||
consistencies = self._state_consistencies
|
||||
else:
|
||||
consistencies = self._consistencies
|
||||
if isinstance(consistencies, list):
|
||||
new_value = []
|
||||
for consistency in consistencies:
|
||||
if load:
|
||||
new_value.append((consistency[0],
|
||||
descr.impl_get_opt_by_path(
|
||||
consistency[1])))
|
||||
else:
|
||||
new_value.append((consistency[0],
|
||||
descr.impl_get_path_by_opt(
|
||||
consistency[1])))
|
||||
|
||||
else:
|
||||
new_value = {}
|
||||
for key, _consistencies in consistencies.items():
|
||||
new_value[key] = []
|
||||
for key_cons, _cons in _consistencies:
|
||||
_list_cons = []
|
||||
for _con in _cons:
|
||||
if load:
|
||||
_list_cons.append(
|
||||
descr.impl_get_opt_by_path(_con))
|
||||
else:
|
||||
_list_cons.append(
|
||||
descr.impl_get_path_by_opt(_con))
|
||||
new_value[key].append((key_cons, tuple(_list_cons)))
|
||||
if load:
|
||||
del(self._state_consistencies)
|
||||
self._consistencies = new_value
|
||||
else:
|
||||
self._state_consistencies = new_value
|
||||
|
||||
def _impl_convert_requires(self, descr, load=False):
|
||||
"""export of the requires during the serialization process
|
||||
|
||||
@ -244,10 +187,7 @@ class BaseOption(object):
|
||||
for func in dir(self):
|
||||
if func.startswith('_impl_convert_'):
|
||||
getattr(self, func)(descr)
|
||||
try:
|
||||
self._state_readonly = self._readonly
|
||||
except AttributeError:
|
||||
pass
|
||||
self._state_readonly = self._readonly
|
||||
|
||||
def __getstate__(self, stated=True):
|
||||
"""special method to enable the serialization with pickle
|
||||
@ -267,7 +207,8 @@ class BaseOption(object):
|
||||
for subclass in self.__class__.__mro__:
|
||||
if subclass is not object:
|
||||
slots.update(subclass.__slots__)
|
||||
slots -= frozenset(['_cache_paths', '__weakref__'])
|
||||
slots -= frozenset(['_cache_paths', '_cache_consistencies',
|
||||
'__weakref__'])
|
||||
states = {}
|
||||
for slot in slots:
|
||||
# remove variable if save variable converted
|
||||
@ -326,13 +267,14 @@ class Option(BaseOption):
|
||||
"""
|
||||
__slots__ = ('_multi', '_validator', '_default_multi', '_default',
|
||||
'_state_callback', '_callback', '_multitype',
|
||||
'_master_slaves', '__weakref__')
|
||||
'_consistencies', '_warnings_only', '_master_slaves',
|
||||
'_state_consistencies', '__weakref__')
|
||||
_empty = ''
|
||||
|
||||
def __init__(self, name, doc, default=None, default_multi=None,
|
||||
requires=None, multi=False, callback=None,
|
||||
callback_params=None, validator=None, validator_params=None,
|
||||
properties=None):
|
||||
properties=None, warnings_only=False):
|
||||
"""
|
||||
:param name: the option's name
|
||||
:param doc: the option's description
|
||||
@ -350,6 +292,8 @@ class Option(BaseOption):
|
||||
validation of the value
|
||||
:param validator_params: the validator's parameters
|
||||
:param properties: tuple of default properties
|
||||
:param warnings_only: _validator and _consistencies don't raise if True
|
||||
Values()._warning contain message
|
||||
|
||||
"""
|
||||
super(Option, self).__init__(name, doc, requires, properties)
|
||||
@ -387,58 +331,70 @@ class Option(BaseOption):
|
||||
default = []
|
||||
self._multitype = multitypes.default
|
||||
self._default_multi = default_multi
|
||||
self._warnings_only = warnings_only
|
||||
self.impl_validate(default)
|
||||
self._default = default
|
||||
self._consistencies = None
|
||||
|
||||
def _launch_consistency(self, func, opt, vals, context, index, opt_):
|
||||
def _launch_consistency(self, func, option, value, context, index,
|
||||
all_cons_opts):
|
||||
"""Launch consistency now
|
||||
|
||||
:param func: function name, this name should start with _cons_
|
||||
:type func: `str`
|
||||
:param option: option that value is changing
|
||||
:type option: `tiramisu.option.Option`
|
||||
:param value: new value of this option
|
||||
:param context: Config's context, if None, check default value instead
|
||||
:type context: `tiramisu.config.Config`
|
||||
:param index: only for multi option, consistency should be launch for
|
||||
specified index
|
||||
:type index: `int`
|
||||
:param all_cons_opts: all options concerne by this consistency
|
||||
:type all_cons_opts: `list` of `tiramisu.option.Option`
|
||||
"""
|
||||
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_)
|
||||
#option is also in all_cons_opts
|
||||
if option not in all_cons_opts:
|
||||
raise ConfigError(_('option not in all_cons_opts'))
|
||||
|
||||
def impl_validate(self, value, context=None, validate=True):
|
||||
all_cons_vals = []
|
||||
for opt in all_cons_opts:
|
||||
#get value
|
||||
if option == opt:
|
||||
opt_value = value
|
||||
else:
|
||||
#if context, calculate value, otherwise get default value
|
||||
if context is not None:
|
||||
opt_value = context._getattr(
|
||||
descr.impl_get_path_by_opt(opt), validate=False)
|
||||
else:
|
||||
opt_value = opt.impl_getdefault()
|
||||
|
||||
#append value
|
||||
if not self.impl_is_multi() or option == opt:
|
||||
all_cons_vals.append(opt_value)
|
||||
else:
|
||||
#value is not already set, could be higher index
|
||||
try:
|
||||
all_cons_vals.append(opt_value[index])
|
||||
except IndexError:
|
||||
#so return if no value
|
||||
return
|
||||
getattr(self, func)(all_cons_opts, all_cons_vals)
|
||||
|
||||
def impl_validate(self, value, context=None, validate=True,
|
||||
force_index=None):
|
||||
"""
|
||||
:param value: the option's value
|
||||
:param context: Config's context
|
||||
:type context: :class:`tiramisu.config.Config`
|
||||
:param validate: if true enables ``self._validator`` validation
|
||||
:type validate: boolean
|
||||
:param force_no_multi: if multi, value has to be a list
|
||||
not if force_no_multi is True
|
||||
:type force_no_multi: boolean
|
||||
"""
|
||||
if not validate:
|
||||
return
|
||||
@ -455,45 +411,44 @@ class Option(BaseOption):
|
||||
validator_params[''] = (val,)
|
||||
else:
|
||||
validator_params = {'': (val,)}
|
||||
ret = carry_out_calculation(self._name, config=context,
|
||||
callback=self._validator[0],
|
||||
callback_params=validator_params)
|
||||
if ret not in [False, True]:
|
||||
raise ConfigError(_('validator should return a boolean, '
|
||||
'not {0}').format(ret))
|
||||
return ret
|
||||
else:
|
||||
return True
|
||||
# Raise ValueError if not valid
|
||||
carry_out_calculation(self._name, config=context,
|
||||
callback=self._validator[0],
|
||||
callback_params=validator_params)
|
||||
|
||||
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__))
|
||||
return
|
||||
# option validation
|
||||
self._validate(_value)
|
||||
try:
|
||||
self._validate(_value)
|
||||
# valid with self._validator
|
||||
val_validator(_value)
|
||||
# if not context launch consistency validation
|
||||
if context is not None:
|
||||
descr._valid_consistency(self, _value, context, _index)
|
||||
self._second_level_validation(_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)
|
||||
msg = _("invalid value {0} for option {1}: {2}").format(
|
||||
_value, self._name, err)
|
||||
if self._warnings_only:
|
||||
warnings.warn_explicit(ValueWarning(msg, self),
|
||||
ValueWarning,
|
||||
self.__class__.__name__, 0)
|
||||
else:
|
||||
raise ValueError(msg)
|
||||
|
||||
# generic calculation
|
||||
if context is not None:
|
||||
descr = context.cfgimpl_get_description()
|
||||
if not self._multi:
|
||||
do_validation(value)
|
||||
|
||||
if not self._multi or force_index is not None:
|
||||
do_validation(value, force_index)
|
||||
else:
|
||||
if not isinstance(value, list):
|
||||
raise ValueError(_("invalid value {0} for option {1} "
|
||||
"which must be a list").format(value,
|
||||
raise ValueError(_("which must be a list").format(value,
|
||||
self._name))
|
||||
for index in range(0, len(value)):
|
||||
val = value[index]
|
||||
for index, val in enumerate(value):
|
||||
do_validation(val, index)
|
||||
|
||||
def impl_getdefault(self, default_multi=False):
|
||||
@ -538,29 +493,45 @@ class Option(BaseOption):
|
||||
def impl_is_multi(self):
|
||||
return self._multi
|
||||
|
||||
def impl_add_consistency(self, func, opt):
|
||||
def impl_add_consistency(self, func, *other_opts):
|
||||
"""Add consistency means that value will be validate with other_opts
|
||||
option's values.
|
||||
|
||||
:param func: function's name
|
||||
:type func: `str`
|
||||
:param other_opts: options used to validate value
|
||||
:type other_opts: `list` of `tiramisu.option.Option`
|
||||
"""
|
||||
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')
|
||||
for opt in other_opts:
|
||||
if not isinstance(opt, Option):
|
||||
raise ConfigError(_('consistency should be set with an option'))
|
||||
if self is opt:
|
||||
raise ConfigError(_('cannot add consistency with itself'))
|
||||
if self.impl_is_multi() != opt.impl_is_multi():
|
||||
raise ConfigError(_('every options in consistency should be '
|
||||
'multi or none'))
|
||||
func = '_cons_{0}'.format(func)
|
||||
self._launch_consistency(func,
|
||||
self,
|
||||
self.impl_getdefault(),
|
||||
None, None, opt)
|
||||
self._consistencies.append((func, opt))
|
||||
all_cons_opts = tuple([self] + list(other_opts))
|
||||
value = self.impl_getdefault()
|
||||
if value is not None:
|
||||
if self.impl_is_multi():
|
||||
for idx, val in enumerate(value):
|
||||
self._launch_consistency(func, self, val, None,
|
||||
idx, all_cons_opts)
|
||||
else:
|
||||
self._launch_consistency(func, self, value, None,
|
||||
None, all_cons_opts)
|
||||
self._consistencies.append((func, all_cons_opts))
|
||||
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))
|
||||
def _cons_not_equal(self, opts, vals):
|
||||
for idx_inf, val_inf in enumerate(vals):
|
||||
for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
|
||||
if val_inf == val_sup is not None:
|
||||
raise ValueError(_("same value for {0} and {1}").format(
|
||||
opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
|
||||
|
||||
def _impl_convert_callbacks(self, descr, load=False):
|
||||
if not load and self._callback is None:
|
||||
@ -596,6 +567,60 @@ class Option(BaseOption):
|
||||
else:
|
||||
self._state_callback = (callback, cllbck_prms)
|
||||
|
||||
# serialize/unserialize
|
||||
def _impl_convert_consistencies(self, descr, load=False):
|
||||
"""during serialization process, many things have to be done.
|
||||
one of them is the localisation of the options.
|
||||
The paths are set once for all.
|
||||
|
||||
:type descr: :class:`tiramisu.option.OptionDescription`
|
||||
:param load: `True` if we are at the init of the option description
|
||||
:type load: bool
|
||||
"""
|
||||
if not load and self._consistencies is None:
|
||||
self._state_consistencies = None
|
||||
elif load and self._state_consistencies is None:
|
||||
self._consistencies = None
|
||||
del(self._state_consistencies)
|
||||
else:
|
||||
if load:
|
||||
consistencies = self._state_consistencies
|
||||
else:
|
||||
consistencies = self._consistencies
|
||||
if isinstance(consistencies, list):
|
||||
new_value = []
|
||||
for consistency in consistencies:
|
||||
values = []
|
||||
for obj in consistency[1]:
|
||||
if load:
|
||||
values.append(descr.impl_get_opt_by_path(obj))
|
||||
else:
|
||||
values.append(descr.impl_get_path_by_opt(obj))
|
||||
new_value.append((consistency[0], tuple(values)))
|
||||
|
||||
else:
|
||||
new_value = {}
|
||||
for key, _consistencies in consistencies.items():
|
||||
new_value[key] = []
|
||||
for key_cons, _cons in _consistencies:
|
||||
_list_cons = []
|
||||
for _con in _cons:
|
||||
if load:
|
||||
_list_cons.append(
|
||||
descr.impl_get_opt_by_path(_con))
|
||||
else:
|
||||
_list_cons.append(
|
||||
descr.impl_get_path_by_opt(_con))
|
||||
new_value[key].append((key_cons, tuple(_list_cons)))
|
||||
if load:
|
||||
del(self._state_consistencies)
|
||||
self._consistencies = new_value
|
||||
else:
|
||||
self._state_consistencies = new_value
|
||||
|
||||
def _second_level_validation(self, value):
|
||||
pass
|
||||
|
||||
|
||||
class ChoiceOption(Option):
|
||||
"""represents a choice out of several objects.
|
||||
@ -609,7 +634,7 @@ class ChoiceOption(Option):
|
||||
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_params=None, properties=()):
|
||||
validator_params=None, properties=None, warnings_only=False):
|
||||
"""
|
||||
:param values: is a list of values the option can possibly take
|
||||
"""
|
||||
@ -628,7 +653,8 @@ class ChoiceOption(Option):
|
||||
multi=multi,
|
||||
validator=validator,
|
||||
validator_params=validator_params,
|
||||
properties=properties)
|
||||
properties=properties,
|
||||
warnings_only=warnings_only)
|
||||
|
||||
def impl_get_values(self):
|
||||
return self._values
|
||||
@ -705,7 +731,7 @@ class SymLinkOption(BaseOption):
|
||||
__slots__ = ('_name', '_opt', '_state_opt')
|
||||
_opt_type = 'symlink'
|
||||
#not return _opt consistencies
|
||||
_consistencies = {}
|
||||
_consistencies = None
|
||||
|
||||
def __init__(self, name, opt):
|
||||
self._name = name
|
||||
@ -731,23 +757,18 @@ class SymLinkOption(BaseOption):
|
||||
del(self._state_opt)
|
||||
super(SymLinkOption, self)._impl_setstate(descr)
|
||||
|
||||
def _impl_convert_consistencies(self, descr, load=False):
|
||||
if load:
|
||||
del(self._state_consistencies)
|
||||
else:
|
||||
self._state_consistencies = None
|
||||
|
||||
|
||||
class IPOption(Option):
|
||||
"represents the choice of an ip"
|
||||
__slots__ = ('_only_private', '_allow_reserved')
|
||||
__slots__ = ('_private_only', '_allow_reserved')
|
||||
_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_params=None,
|
||||
properties=None, only_private=False, allow_reserved=False):
|
||||
self._only_private = only_private
|
||||
properties=None, private_only=False, allow_reserved=False,
|
||||
warnings_only=False):
|
||||
self._private_only = private_only
|
||||
self._allow_reserved = allow_reserved
|
||||
super(IPOption, self).__init__(name, doc, default=default,
|
||||
default_multi=default_multi,
|
||||
@ -757,13 +778,20 @@ class IPOption(Option):
|
||||
multi=multi,
|
||||
validator=validator,
|
||||
validator_params=validator_params,
|
||||
properties=properties)
|
||||
properties=properties,
|
||||
warnings_only=warnings_only)
|
||||
|
||||
def _validate(self, value):
|
||||
try:
|
||||
IP('{0}/32'.format(value))
|
||||
except ValueError:
|
||||
raise ValueError(_('invalid IP {0}').format(self._name))
|
||||
|
||||
def _second_level_validation(self, value):
|
||||
ip = IP('{0}/32'.format(value))
|
||||
if not self._allow_reserved and ip.iptype() == 'RESERVED':
|
||||
raise ValueError(_("IP mustn't not be in reserved class"))
|
||||
if self._only_private and not ip.iptype() == 'PRIVATE':
|
||||
if self._private_only and not ip.iptype() == 'PRIVATE':
|
||||
raise ValueError(_("IP must be in private class"))
|
||||
|
||||
|
||||
@ -785,7 +813,7 @@ class PortOption(Option):
|
||||
callback_params=None, validator=None, validator_params=None,
|
||||
properties=None, allow_range=False, allow_zero=False,
|
||||
allow_wellknown=True, allow_registred=True,
|
||||
allow_private=False):
|
||||
allow_private=False, warnings_only=False):
|
||||
self._allow_range = allow_range
|
||||
self._min_value = None
|
||||
self._max_value = None
|
||||
@ -817,7 +845,8 @@ class PortOption(Option):
|
||||
multi=multi,
|
||||
validator=validator,
|
||||
validator_params=validator_params,
|
||||
properties=properties)
|
||||
properties=properties,
|
||||
warnings_only=warnings_only)
|
||||
|
||||
def _validate(self, value):
|
||||
if self._allow_range and ":" in str(value):
|
||||
@ -842,6 +871,12 @@ class NetworkOption(Option):
|
||||
_opt_type = 'network'
|
||||
|
||||
def _validate(self, value):
|
||||
try:
|
||||
IP(value)
|
||||
except ValueError:
|
||||
raise ValueError(_('invalid network address {0}').format(self._name))
|
||||
|
||||
def _second_level_validation(self, value):
|
||||
ip = IP(value)
|
||||
if ip.iptype() == 'RESERVED':
|
||||
raise ValueError(_("network shall not be in reserved class"))
|
||||
@ -853,18 +888,26 @@ class NetmaskOption(Option):
|
||||
_opt_type = 'netmask'
|
||||
|
||||
def _validate(self, value):
|
||||
IP('0.0.0.0/{0}'.format(value))
|
||||
try:
|
||||
IP('0.0.0.0/{0}'.format(value))
|
||||
except ValueError:
|
||||
raise ValueError(_('invalid netmask address {0}').format(self._name))
|
||||
|
||||
def _cons_network_netmask(self, optname, value, value_):
|
||||
def _cons_network_netmask(self, opts, vals):
|
||||
#opts must be (netmask, network) options
|
||||
self.__cons_netmask(optname, value, value_, False)
|
||||
if None in vals:
|
||||
return
|
||||
self.__cons_netmask(opts, vals[0], vals[1], False)
|
||||
|
||||
def _cons_ip_netmask(self, optname, value, value_):
|
||||
def _cons_ip_netmask(self, opts, vals):
|
||||
#opts must be (netmask, ip) options
|
||||
self.__cons_netmask(optname, value, value_, True)
|
||||
if None in vals:
|
||||
return
|
||||
self.__cons_netmask(opts, vals[0], vals[1], True)
|
||||
|
||||
#def __cons_netmask(self, opt, value, context, index, opts, make_net):
|
||||
def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
|
||||
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
|
||||
if len(opts) != 2:
|
||||
raise ConfigError(_('invalid len for opts'))
|
||||
msg = None
|
||||
try:
|
||||
ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
|
||||
@ -890,23 +933,48 @@ class NetmaskOption(Option):
|
||||
else:
|
||||
msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
|
||||
if msg is not None:
|
||||
raise ValueError(msg.format(val_ipnetwork, optname,
|
||||
raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
|
||||
val_netmask, self._name))
|
||||
|
||||
|
||||
class BroadcastOption(Option):
|
||||
__slots__ = tuple()
|
||||
_opt_type = 'broadcast'
|
||||
|
||||
def _validate(self, value):
|
||||
try:
|
||||
IP('{0}/32'.format(value))
|
||||
except ValueError:
|
||||
raise ValueError(_('invalid broadcast address {0}').format(self._name))
|
||||
|
||||
def _cons_broadcast(self, opts, vals):
|
||||
if len(vals) != 3:
|
||||
raise ConfigError(_('invalid len for vals'))
|
||||
if None in vals:
|
||||
return
|
||||
broadcast, network, netmask = vals
|
||||
if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
|
||||
raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
|
||||
'({3}) and netmask {4} ({5})').format(
|
||||
broadcast, opts[0]._name, network,
|
||||
opts[1]._name, netmask, opts[2]._name))
|
||||
|
||||
|
||||
class DomainnameOption(Option):
|
||||
"represents the choice of a domain name"
|
||||
"""represents the choice of a domain name
|
||||
netbios: for MS domain
|
||||
hostname: to identify the device
|
||||
domainname:
|
||||
fqdn: with tld, not supported yet
|
||||
"""
|
||||
__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_params=None,
|
||||
properties=None, allow_ip=False, type_='domainname'):
|
||||
#netbios: for MS domain
|
||||
#hostname: to identify the device
|
||||
#domainname:
|
||||
#fqdn: with tld, not supported yet
|
||||
properties=None, allow_ip=False, type_='domainname',
|
||||
warnings_only=False):
|
||||
if type_ not in ['netbios', 'hostname', 'domainname']:
|
||||
raise ValueError(_('unknown type_ {0} for hostname').format(type_))
|
||||
self._type = type_
|
||||
@ -921,7 +989,8 @@ class DomainnameOption(Option):
|
||||
multi=multi,
|
||||
validator=validator,
|
||||
validator_params=validator_params,
|
||||
properties=properties)
|
||||
properties=properties,
|
||||
warnings_only=warnings_only)
|
||||
|
||||
def _validate(self, value):
|
||||
if self._allow_ip is True:
|
||||
@ -959,9 +1028,9 @@ class OptionDescription(BaseOption):
|
||||
"""
|
||||
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
|
||||
'_state_group_type', '_properties', '_children',
|
||||
'_consistencies', '_calc_properties', '__weakref__',
|
||||
'_cache_consistencies', '_calc_properties', '__weakref__',
|
||||
'_readonly', '_impl_informations', '_state_requires',
|
||||
'_state_consistencies', '_stated', '_state_readonly')
|
||||
'_stated', '_state_readonly')
|
||||
_opt_type = 'optiondescription'
|
||||
|
||||
def __init__(self, name, doc, children, requires=None, properties=None):
|
||||
@ -982,6 +1051,7 @@ class OptionDescription(BaseOption):
|
||||
old = child
|
||||
self._children = (tuple(child_names), tuple(children))
|
||||
self._cache_paths = None
|
||||
self._cache_consistencies = None
|
||||
# the group_type is useful for filtering OptionDescriptions in a config
|
||||
self._group_type = groups.default
|
||||
|
||||
@ -1055,12 +1125,11 @@ class OptionDescription(BaseOption):
|
||||
if not force_no_consistencies and \
|
||||
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))
|
||||
func, all_cons_opts = consistency
|
||||
for opt in all_cons_opts:
|
||||
_consistencies.setdefault(opt,
|
||||
[]).append((func,
|
||||
all_cons_opts))
|
||||
else:
|
||||
_currpath.append(attr)
|
||||
option.impl_build_cache(cache_path,
|
||||
@ -1072,7 +1141,12 @@ class OptionDescription(BaseOption):
|
||||
if save:
|
||||
self._cache_paths = (tuple(cache_option), tuple(cache_path))
|
||||
if not force_no_consistencies:
|
||||
self._consistencies = _consistencies
|
||||
if _consistencies != {}:
|
||||
self._cache_consistencies = {}
|
||||
for opt, cons in _consistencies.items():
|
||||
if opt not in cache_option:
|
||||
raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
|
||||
self._cache_consistencies[opt] = tuple(cons)
|
||||
self._readonly = True
|
||||
|
||||
def impl_get_opt_by_path(self, path):
|
||||
@ -1143,17 +1217,18 @@ class OptionDescription(BaseOption):
|
||||
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)
|
||||
def _valid_consistency(self, option, value, context, index):
|
||||
if self._cache_consistencies is None:
|
||||
return True
|
||||
#consistencies is something like [('_cons_not_equal', (opt1, opt2))]
|
||||
consistencies = self._cache_consistencies.get(option)
|
||||
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])
|
||||
for func, all_cons_opts in consistencies:
|
||||
#all_cons_opts[0] is the option where func is set
|
||||
ret = all_cons_opts[0]._launch_consistency(func, option,
|
||||
value,
|
||||
context, index,
|
||||
all_cons_opts)
|
||||
if ret is False:
|
||||
return False
|
||||
return True
|
||||
@ -1193,6 +1268,7 @@ class OptionDescription(BaseOption):
|
||||
"""
|
||||
if descr is None:
|
||||
self._cache_paths = None
|
||||
self._cache_consistencies = None
|
||||
self.impl_build_cache(force_no_consistencies=True)
|
||||
descr = self
|
||||
self._group_type = getattr(groups, self._state_group_type)
|
||||
@ -1324,6 +1400,6 @@ def validate_callback(callback, callback_params, type_):
|
||||
).format(type_, type(option)))
|
||||
if force_permissive not in [True, False]:
|
||||
raise ValueError(_('{0}_params should have a boolean'
|
||||
'not a {0} for second argument'
|
||||
' not a {0} for second argument'
|
||||
).format(type_, type(
|
||||
force_permissive)))
|
||||
|
@ -104,7 +104,8 @@ class Values(object):
|
||||
path = self._get_opt_path(opt)
|
||||
if self._p_.hasvalue(path):
|
||||
setting = self.context().cfgimpl_get_settings()
|
||||
opt.impl_validate(opt.impl_getdefault(), self.context(),
|
||||
opt.impl_validate(opt.impl_getdefault(),
|
||||
self.context(),
|
||||
'validator' in setting)
|
||||
self.context().cfgimpl_reset_cache()
|
||||
if (opt.impl_is_multi() and
|
||||
@ -237,7 +238,7 @@ class Values(object):
|
||||
force_properties=force_properties,
|
||||
force_permissives=force_permissives)
|
||||
if config_error is not None:
|
||||
raise ConfigError(config_error)
|
||||
raise config_error
|
||||
return value
|
||||
|
||||
def __setitem__(self, opt, value):
|
||||
@ -368,6 +369,7 @@ class Values(object):
|
||||
def __setstate__(self, states):
|
||||
self._p_ = states['_p_']
|
||||
|
||||
|
||||
# ____________________________________________________________
|
||||
# multi types
|
||||
|
||||
@ -451,10 +453,10 @@ class Multi(list):
|
||||
value_slave.append(slave.impl_getdefault_multi(),
|
||||
force=True)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._validate(value)
|
||||
def __setitem__(self, index, value):
|
||||
self._validate(value, index)
|
||||
#assume not checking mandatory property
|
||||
super(Multi, self).__setitem__(key, value)
|
||||
super(Multi, self).__setitem__(index, value)
|
||||
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
|
||||
|
||||
def append(self, value, force=False):
|
||||
@ -472,15 +474,17 @@ class Multi(list):
|
||||
#Force None il return a list
|
||||
if isinstance(value, list):
|
||||
value = None
|
||||
self._validate(value)
|
||||
index = self.__len__()
|
||||
self._validate(value, index)
|
||||
super(Multi, self).append(value)
|
||||
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
|
||||
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path,
|
||||
self,
|
||||
validate_properties=not force)
|
||||
if not force and self.opt.impl_get_multitype() == multitypes.master:
|
||||
for slave in self.opt.impl_get_master_slaves():
|
||||
path = values._get_opt_path(slave)
|
||||
if not values._is_default_owner(path):
|
||||
if slave.impl_has_callback():
|
||||
index = self.__len__() - 1
|
||||
dvalue = values._getcallback_value(slave, index=index)
|
||||
else:
|
||||
dvalue = slave.impl_getdefault_multi()
|
||||
@ -532,10 +536,11 @@ class Multi(list):
|
||||
super(Multi, self).extend(iterable)
|
||||
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
|
||||
|
||||
def _validate(self, value):
|
||||
def _validate(self, value, force_index):
|
||||
if value is not None:
|
||||
try:
|
||||
self.opt._validate(value)
|
||||
self.opt.impl_validate(value, context=self.context(),
|
||||
force_index=force_index)
|
||||
except ValueError as err:
|
||||
raise ValueError(_("invalid value {0} "
|
||||
"for option {1}: {2}"
|
||||
|
Reference in New Issue
Block a user