Values in ChoiceOption can be a function now
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 27 Apr 2014 08:32:40 +0000 (10:32 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 27 Apr 2014 08:36:47 +0000 (10:36 +0200)
ChangeLog
tiramisu/autolib.py
tiramisu/error.py
tiramisu/option/baseoption.py
tiramisu/option/option.py

index 4794942..67fb6af 100644 (file)
--- a/ChangeLog
+++ b/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:
        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:
        if slave has a default value greater than master's one, it's raise
index 3e3eea7..48f61e0 100644 (file)
@@ -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:
index 6a7c8be..e4955bb 100644 (file)
@@ -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
index 2ad51dc..d7c1a69 100644 (file)
@@ -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,
index 1b39012..9aeb7d5 100644 (file)
 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'))