Values in ChoiceOption can be a function now
This commit is contained in:
parent
9112a8c5b0
commit
31fff062e1
5 changed files with 76 additions and 36 deletions
10
ChangeLog
10
ChangeLog
|
@ -1,9 +1,15 @@
|
||||||
XXXXXXXXXXXXX Emmanuel Garette <egarette@cadoles.com>
|
Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
|
|
||||||
|
* add dynamic ChoiceOption:
|
||||||
|
we can have dynamic ChoiceOption. Parameter values can be a function
|
||||||
|
and as callback, we can add values_params
|
||||||
|
|
||||||
|
Fri Apr 25 22:57:08 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
|
|
||||||
* add SubMulti:
|
* add SubMulti:
|
||||||
a SubMulti is a multi in a multi variable
|
a SubMulti is a multi in a multi variable
|
||||||
|
|
||||||
Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette <egarette@cadoles.com>
|
Sat Apr 12 11:37:27 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
|
|
||||||
* behavior change in master/slave part of code:
|
* behavior change in master/slave part of code:
|
||||||
if slave has a default value greater than master's one, it's raise
|
if slave has a default value greater than master's one, it's raise
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# the whole pypy projet is under MIT licence
|
# the whole pypy projet is under MIT licence
|
||||||
# ____________________________________________________________
|
# ____________________________________________________________
|
||||||
"enables us to carry out a calculation and return an option's value"
|
"enables us to carry out a calculation and return an option's value"
|
||||||
from tiramisu.error import PropertiesOptionError, ConfigError
|
from tiramisu.error import PropertiesOptionError, ConfigError, ContextError
|
||||||
from tiramisu.i18n import _
|
from tiramisu.i18n import _
|
||||||
from tiramisu.setting import undefined
|
from tiramisu.setting import undefined
|
||||||
# ____________________________________________________________
|
# ____________________________________________________________
|
||||||
|
@ -143,6 +143,8 @@ def carry_out_calculation(option, config, callback, callback_params,
|
||||||
for key, callbacks in callback_params.items():
|
for key, callbacks in callback_params.items():
|
||||||
for callbk in callbacks:
|
for callbk in callbacks:
|
||||||
if isinstance(callbk, tuple):
|
if isinstance(callbk, tuple):
|
||||||
|
if config is None:
|
||||||
|
raise ContextError()
|
||||||
if len(callbk) == 1:
|
if len(callbk) == 1:
|
||||||
tcparams.setdefault(key, []).append((config, False))
|
tcparams.setdefault(key, []).append((config, False))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -34,6 +34,12 @@ class ConfigError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ContextError(Exception):
|
||||||
|
"""context needed but not given
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConflictError(Exception):
|
class ConflictError(Exception):
|
||||||
"duplicate options are present in a single config"
|
"duplicate options are present in a single config"
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -500,7 +500,7 @@ class Option(OnlyOption):
|
||||||
validator_params = {}
|
validator_params = {}
|
||||||
for val_param, values in self._validator_params.items():
|
for val_param, values in self._validator_params.items():
|
||||||
validator_params[val_param] = values
|
validator_params[val_param] = values
|
||||||
#FIXME ... ca sert à quoi ...
|
#inject value in calculation
|
||||||
if '' in validator_params:
|
if '' in validator_params:
|
||||||
lst = list(validator_params[''])
|
lst = list(validator_params[''])
|
||||||
lst.insert(0, val)
|
lst.insert(0, val)
|
||||||
|
@ -519,7 +519,7 @@ class Option(OnlyOption):
|
||||||
return
|
return
|
||||||
# option validation
|
# option validation
|
||||||
try:
|
try:
|
||||||
self._validate(_value)
|
self._validate(_value, context)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
log.debug('do_validation: value: {0}, index: {1}, '
|
log.debug('do_validation: value: {0}, index: {1}, '
|
||||||
'submulti_index: {2}'.format(_value, _index,
|
'submulti_index: {2}'.format(_value, _index,
|
||||||
|
|
|
@ -22,10 +22,13 @@
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from IPy import IP
|
from IPy import IP
|
||||||
|
from types import FunctionType
|
||||||
|
from tiramisu.setting import log
|
||||||
|
|
||||||
from tiramisu.error import ConfigError
|
from tiramisu.error import ConfigError, ContextError
|
||||||
from tiramisu.i18n import _
|
from tiramisu.i18n import _
|
||||||
from .baseoption import Option
|
from .baseoption import Option, validate_callback
|
||||||
|
from tiramisu.autolib import carry_out_calculation
|
||||||
|
|
||||||
|
|
||||||
class ChoiceOption(Option):
|
class ChoiceOption(Option):
|
||||||
|
@ -35,20 +38,25 @@ class ChoiceOption(Option):
|
||||||
"""
|
"""
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def __init__(self, name, doc, values, default=None, default_multi=None,
|
def __init__(self, name, doc, values, default=None,
|
||||||
requires=None, multi=False, callback=None,
|
values_params=None, default_multi=None, requires=None,
|
||||||
callback_params=None, open_values=False, validator=None,
|
multi=False, callback=None, callback_params=None,
|
||||||
validator_params=None, properties=None, warnings_only=False):
|
open_values=False, validator=None, validator_params=None,
|
||||||
|
properties=None, warnings_only=False):
|
||||||
"""
|
"""
|
||||||
:param values: is a list of values the option can possibly take
|
:param values: is a list of values the option can possibly take
|
||||||
"""
|
"""
|
||||||
if not isinstance(values, tuple):
|
if isinstance(values, FunctionType):
|
||||||
raise TypeError(_('values must be a tuple for {0}').format(name))
|
validate_callback(values, values_params, 'values')
|
||||||
|
elif not isinstance(values, tuple):
|
||||||
|
raise TypeError(_('values must be a tuple or a function for {0}'
|
||||||
|
).format(name))
|
||||||
if open_values not in (True, False):
|
if open_values not in (True, False):
|
||||||
raise TypeError(_('open_values must be a boolean for '
|
raise TypeError(_('open_values must be a boolean for '
|
||||||
'{0}').format(name))
|
'{0}').format(name))
|
||||||
self._extra = {'_choice_open_values': open_values,
|
self._extra = {'_choice_open_values': open_values,
|
||||||
'_choice_values': values}
|
'_choice_values': values,
|
||||||
|
'_choice_values_params': values_params}
|
||||||
super(ChoiceOption, self).__init__(name, doc, default=default,
|
super(ChoiceOption, self).__init__(name, doc, default=default,
|
||||||
default_multi=default_multi,
|
default_multi=default_multi,
|
||||||
callback=callback,
|
callback=callback,
|
||||||
|
@ -60,24 +68,42 @@ class ChoiceOption(Option):
|
||||||
properties=properties,
|
properties=properties,
|
||||||
warnings_only=warnings_only)
|
warnings_only=warnings_only)
|
||||||
|
|
||||||
def impl_get_values(self):
|
def impl_get_values(self, context):
|
||||||
return self._extra['_choice_values']
|
#FIXME cache? but in context...
|
||||||
|
values = self._extra['_choice_values']
|
||||||
|
if isinstance(values, FunctionType):
|
||||||
|
values_params = self._extra['_choice_values_params']
|
||||||
|
if values_params is None:
|
||||||
|
values_params = {}
|
||||||
|
values = carry_out_calculation(self, config=context,
|
||||||
|
callback=values,
|
||||||
|
callback_params=values_params)
|
||||||
|
if not isinstance(values, list):
|
||||||
|
raise ConfigError(_('calculated values for {0} is not a list'
|
||||||
|
'').format(self.impl_getname()))
|
||||||
|
return values
|
||||||
|
|
||||||
def impl_is_openvalues(self):
|
def impl_is_openvalues(self):
|
||||||
return self._extra['_choice_open_values']
|
return self._extra['_choice_open_values']
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not self.impl_is_openvalues() and not value in self.impl_get_values():
|
try:
|
||||||
|
values = self.impl_get_values(context)
|
||||||
|
if not self.impl_is_openvalues() and \
|
||||||
|
not value in values:
|
||||||
raise ValueError(_('value {0} is not permitted, '
|
raise ValueError(_('value {0} is not permitted, '
|
||||||
'only {1} is allowed'
|
'only {1} is allowed'
|
||||||
'').format(value, self._extra['_choice_values']))
|
'').format(value,
|
||||||
|
values))
|
||||||
|
except ContextError:
|
||||||
|
log.debug('ChoiceOption validation, disabled because no context')
|
||||||
|
|
||||||
|
|
||||||
class BoolOption(Option):
|
class BoolOption(Option):
|
||||||
"represents a choice between ``True`` and ``False``"
|
"represents a choice between ``True`` and ``False``"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not isinstance(value, bool):
|
if not isinstance(value, bool):
|
||||||
raise ValueError(_('invalid boolean'))
|
raise ValueError(_('invalid boolean'))
|
||||||
|
|
||||||
|
@ -86,7 +112,7 @@ class IntOption(Option):
|
||||||
"represents a choice of an integer"
|
"represents a choice of an integer"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not isinstance(value, int):
|
if not isinstance(value, int):
|
||||||
raise ValueError(_('invalid integer'))
|
raise ValueError(_('invalid integer'))
|
||||||
|
|
||||||
|
@ -95,7 +121,7 @@ class FloatOption(Option):
|
||||||
"represents a choice of a floating point number"
|
"represents a choice of a floating point number"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not isinstance(value, float):
|
if not isinstance(value, float):
|
||||||
raise ValueError(_('invalid float'))
|
raise ValueError(_('invalid float'))
|
||||||
|
|
||||||
|
@ -104,7 +130,7 @@ class StrOption(Option):
|
||||||
"represents the choice of a string"
|
"represents the choice of a string"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise ValueError(_('invalid string'))
|
raise ValueError(_('invalid string'))
|
||||||
|
|
||||||
|
@ -120,7 +146,7 @@ else:
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
_empty = u''
|
_empty = u''
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if not isinstance(value, unicode):
|
if not isinstance(value, unicode):
|
||||||
raise ValueError(_('invalid unicode'))
|
raise ValueError(_('invalid unicode'))
|
||||||
|
|
||||||
|
@ -147,7 +173,7 @@ class IPOption(Option):
|
||||||
properties=properties,
|
properties=properties,
|
||||||
warnings_only=warnings_only)
|
warnings_only=warnings_only)
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
# sometimes an ip term starts with a zero
|
# sometimes an ip term starts with a zero
|
||||||
# but this does not fit in some case, for example bind does not like it
|
# but this does not fit in some case, for example bind does not like it
|
||||||
try:
|
try:
|
||||||
|
@ -248,7 +274,7 @@ class PortOption(Option):
|
||||||
properties=properties,
|
properties=properties,
|
||||||
warnings_only=warnings_only)
|
warnings_only=warnings_only)
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if self._extra['_allow_range'] and ":" in str(value):
|
if self._extra['_allow_range'] and ":" in str(value):
|
||||||
value = str(value).split(':')
|
value = str(value).split(':')
|
||||||
if len(value) != 2:
|
if len(value) != 2:
|
||||||
|
@ -275,7 +301,7 @@ class NetworkOption(Option):
|
||||||
"represents the choice of a network"
|
"represents the choice of a network"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
try:
|
try:
|
||||||
IP(value)
|
IP(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -295,7 +321,7 @@ class NetmaskOption(Option):
|
||||||
"represents the choice of a netmask"
|
"represents the choice of a netmask"
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
try:
|
try:
|
||||||
IP('0.0.0.0/{0}'.format(value))
|
IP('0.0.0.0/{0}'.format(value))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -344,7 +370,7 @@ class NetmaskOption(Option):
|
||||||
class BroadcastOption(Option):
|
class BroadcastOption(Option):
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
try:
|
try:
|
||||||
IP('{0}/32'.format(value))
|
IP('{0}/32'.format(value))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -418,7 +444,7 @@ class DomainnameOption(Option):
|
||||||
properties=properties,
|
properties=properties,
|
||||||
warnings_only=warnings_only)
|
warnings_only=warnings_only)
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
if self._extra['_allow_ip'] is True:
|
if self._extra['_allow_ip'] is True:
|
||||||
try:
|
try:
|
||||||
IP('{0}/32'.format(value))
|
IP('{0}/32'.format(value))
|
||||||
|
@ -440,7 +466,7 @@ class EmailOption(DomainnameOption):
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
|
username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
splitted = value.split('@', 1)
|
splitted = value.split('@', 1)
|
||||||
try:
|
try:
|
||||||
username, domain = splitted
|
username, domain = splitted
|
||||||
|
@ -457,7 +483,7 @@ class URLOption(DomainnameOption):
|
||||||
proto_re = re.compile(r'(http|https)://')
|
proto_re = re.compile(r'(http|https)://')
|
||||||
path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
|
path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
match = self.proto_re.search(value)
|
match = self.proto_re.search(value)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError(_('invalid url, must start with http:// or '
|
raise ValueError(_('invalid url, must start with http:// or '
|
||||||
|
@ -493,7 +519,7 @@ class UsernameOption(Option):
|
||||||
#regexp build with 'man 8 adduser' informations
|
#regexp build with 'man 8 adduser' informations
|
||||||
username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
|
username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
match = self.username_re.search(value)
|
match = self.username_re.search(value)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError(_('invalid username'))
|
raise ValueError(_('invalid username'))
|
||||||
|
@ -503,7 +529,7 @@ class FilenameOption(Option):
|
||||||
__slots__ = tuple()
|
__slots__ = tuple()
|
||||||
path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
|
path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
|
||||||
|
|
||||||
def _validate(self, value):
|
def _validate(self, value, context=None):
|
||||||
match = self.path_re.search(value)
|
match = self.path_re.search(value)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError(_('invalid filename'))
|
raise ValueError(_('invalid filename'))
|
||||||
|
|
Loading…
Reference in a new issue