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