diff --git a/doc/config.txt b/doc/config.txt index d9a1f5a..17bddf3 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -267,15 +267,19 @@ and of course, :meth:`~config.SubConfig.make_dict()` can be called in a subtree: the owners ~~~~~~~~~~~ -When a value is set on an option, an owner is set too, that's why one can know -at any time if a value is a default value or not. Let's create a tree:: +.. glossary:: - >>> var1 = UnicodeOption('var1', '', u'oui') - >>> od1 = OptionDescription('od1', '', [var1]) - >>> rootod = OptionDescription('rootod', '', [od1]) - >>> c = Config(rootod) - >>> c.read_write() - + owner + + When a value is set on an option, an owner is set too, that's why one can know + at any time if a value is a default value or not. Let's create a tree:: + + >>> var1 = UnicodeOption('var1', '', u'oui') + >>> od1 = OptionDescription('od1', '', [var1]) + >>> rootod = OptionDescription('rootod', '', [od1]) + >>> c = Config(rootod) + >>> c.read_write() + Then let's retrieve the owner associated to an option:: >>> print c.getowner('var1') @@ -399,184 +403,135 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property tiramisu.error.PropertiesOptionError: trying to access to an option named: var1 with properties ['hidden'] -The requirements -~~~~~~~~~~~~~~~~~~ -Let's create an option wich has requirements:: - - >>> from tiramisu.option import * - >>> from tiramisu.config import * - >>> var2 = UnicodeOption('var2', '', u'oui') - >>> var1 = UnicodeOption('var1', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}]) - >>> var3 = UnicodeOption('var3', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}, {'option':var2, 'expected':u'non', 'action':'disabled'}]) - >>> var4 = UnicodeOption('var4', '', u'oui') - >>> od1 = OptionDescription('od1', '', [var1, var2, var3]) - >>> od2 = OptionDescription('od2', '', [var4], requires=[{'option':od1.var2, 'expected':u'oui', 'action':'hidden', 'inverse':True}]) - >>> rootod = OptionDescription('rootod', '', [od1, od2]) - >>> c = Config(rootod) - >>> c.read_write() - -The requirement here is the dict `{'option':var2, 'expected':u'non', -'action':'hidden'}` wich means that is the option `'od1.var2'` is set to -`'non'`, the option `'od1.var1'` is gonna be hidden. On the other hand, if the -option `'od1.var2'` is different from `'non'`, the option `'od1.var1'` is not -hidden any more:: - - >>> print c.cfgimpl_get_settings()[rootod.od1.var1] - [] - >>> print c.od1.var1 - value - >>> print c.od1.var2 - oui - >>> c.od1.var2 = u'non' - >>> print c.cfgimpl_get_settings()[rootod.od1.var1] - ['hidden'] - >>> print c.od1.var1 - Traceback (most recent call last): - [...] - tiramisu.error.PropertiesOptionError: trying to access to an option named: - var1 with properties ['hidden'] - >>> c.od1.var2 = u'oui' - >>> print c.cfgimpl_get_settings()[rootod.od1.var1] - [] - >>> print c.od1.var1 - value +.. _multi-option: -The requirement on `od2` is `{'option':od1.var2, 'expected':u'oui', -'action':'hidden', 'inverse':True}`, which means that if the option `od1.var2` -is set to `oui`, the option is not hidden (because of the `True` at the end of -the tuple wich means 'inverted', take a look at the :doc:`consistency` -document.):: +The multi-options +~~~~~~~~~~~~~~~~~~~~~ - >>> print c.od2.var4 - oui - >>> c.od1.var2 = u'non' - >>> print c.od2.var4 - Traceback (most recent call last): - [...] - tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden'] - >>> c.od1.var2 = u'oui' - >>> print c.od2.var4 - oui - -Requirements can be accumulated +.. glossary:: - >>> print c.cfgimpl_get_settings()[rootod.od1.var3] - [] - >>> c.od1.var2 = u'non' - >>> print c.cfgimpl_get_settings()[rootod.od1.var3] - ['disabled', 'hidden'] - >>> c.od1.var2 = u'oui' - >>> print c.cfgimpl_get_settings()[rootod.od1.var3] - [] + multi-option -Requirements can be accumulated for different or identical properties (inverted -or not):: - - >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, - 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', - 'action':'hidden'}]) - >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, - 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', - 'action':'disabled', 'inverse':True}]) - -But it is not possible to have inverted requirements on the same property. -Here is an impossible situation:: - - >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, - 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', - 'hidden', True}]) - - Traceback (most recent call last): - [...] - ValueError: inconsistency in action types for option: var3 action: hidden + Multi-options are normal options that have list of values (multiple values) + instead of values:: + + >>> var1 = UnicodeOption('var1', '', [u'val1', u'val2'], multi=True) + >>> od1 = OptionDescription('od1', '', [var1]) + >>> rootod = OptionDescription('rootod', '', [od1]) + >>> c = Config(rootod) + >>> c.read_write() -The calculations -~~~~~~~~~~~~~~~~~ - -Let's create four calculation functions:: - - def return_calc(): - #return an unicode value - return u'calc' - - def return_value(value): - return value - - def return_value_param(param=u''): - return param - - def return_no_value_if_non(value): - #if value is not u'non' return value - if value == u'non': - return None - else: - return value - -Then we create four options using theses functions:: - - >>> var1 = UnicodeOption('var1', '', callback=return_calc) - >>> var2 = UnicodeOption('var2', '', callback=return_value, callback_params={'': (u'value',)}) - >>> var3 = UnicodeOption('var3', '', callback=return_value_param, callback_params={'param': (u'value_param',)}) - >>> var4 = UnicodeOption('var4', '', callback=return_no_value_if_non, callback_params={'': (('od1.var5', False),)}) - >>> var5 = UnicodeOption('var5', '', u'oui') - >>> od1 = OptionDescription('od1', '', [var1, var2, var3, var4, var5]) - >>> rootod = OptionDescription('rootod', '', [od1]) - >>> c = Config(rootod) - >>> c.read_write() - -The first option `var1` returns the result of the `return_calc` function, wich -is `u'calc'`:: +A multi-option's value can be manipulated like a list:: >>> print c.od1.var1 - calc + [u'val1', u'val2'] + >>> c.od1.var1 = [u'var1'] + >>> print c.od1.var1 + [u'var1'] + >>> c.od1.var1.append(u'val3') + >>> print c.od1.var1 + [u'var1', u'val3'] + >>> c.od1.var1.pop(1) + u'val3' + >>> print c.od1.var1 + [u'var1'] -The second option `var2` returns the result of the `return_value` fucntion, -wich is `value`. The parameter `u'value'` is passed to this function:: +But it is not possible to set a value to a multi-option wich is not a list:: - >>> print c.od1.var2 - value - -The third option `var3` returns the result of the function `return_value_param` -with the named parameter `param` and the value `u'value_param'`:: - - >>> print c.od1.var3 - value_param - -The fourth option `var4` returns the reslut of the function `return_no_value_if_non` -that is the value of `od1.var5` exceptif the value is u`non`:: - - >>> print c.od1.var4 - oui - >>> c.od1.var5 = u'new' - >>> print c.od1.var4 - new - >>> c.od1.var5 = u'non' - >>> print c.od1.var4 - None + >>> c.od1.var1 = u'error' + Traceback (most recent call last): + [...] + ValueError: invalid value error for option var1 which must be a list -The calculation replaces the default value. -If we modify the value, the calculation is not carried out any more:: - >>> print c.od1.var1 - calc - >>> c.od1.var1 = u'new_value' - >>> print c.od1.var1 - new_value - -To force the calculation to be carried out in some cases, one must add the -`frozen` and the `force_default_on_freeze` properties:: +The master/slave groups +~~~~~~~~~~~~~~~~~~~~~~~~~ - >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('frozen') - >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('force_default_on_freeze') - >>> print c.od1.var1 - calc - >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('frozen') - >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('force_default_on_freeze') - >>> print c.od1.var1 - new_value + +.. glossary:: + + master/slave + + A master/slave group is an :class:`~tiramisu.option.OptionDescription` and the + options that lives inside. + + Inside this group, a special option, named master option, has the same name as + the group. The group (the option description) is set to type `master`. + All options in a master group is a multi-option (see :ref:`multi-option`). + The slave options have a `default_multi` attribute set to `True`:: + + >>> from tiramisu.setting import groups + >>> from tiramisu.config import Config + >>> from tiramisu.option import UnicodeOption, OptionDescription + >>> + >>> var1 = UnicodeOption('master', '', multi=True) + >>> var2 = UnicodeOption('slave1', '', multi=True) + >>> var3 = UnicodeOption('slave2', '', multi=True, default_multi=u"default") + >>> + >>> od1 = OptionDescription('master', '', [var1, var2, var3]) + >>> od1.impl_set_group_type(groups.master) + >>> + >>> rootod = OptionDescription('rootod', '', [od1]) + >>> c = Config(rootod) + >>> c.read_write() + +The length of the lists can be modified:: + + >>> print c.master + master = [] + slave1 = [] + slave2 = [] + >>> c.master.master.append(u'oui') + >>> print c.master + master = [u'oui'] + slave1 = [None] + slave2 = [u'default'] + >>> c.master.master = [u'non'] + >>> print c.master + master = [u'non'] + slave1 = [None] + slave2 = [u'default'] + >>> + >>> c.master.master = [u'oui', u'non'] + >>> print c.master + master = [u'oui', u'non'] + slave1 = [None, None] + slave2 = [u'default', u'default'] + +But it is forbidden to change the lenght of a slave:: + + >>> c.master.slave1[0] = u'super' + >>> print c.master + master = [u'oui', u'non'] + slave1 = [u'super', None] + slave2 = [u'default', u'default'] + >>> c.master.slave1 = [u'new1', u'new2'] + >>> print c.master + master = [u'oui', u'non'] + slave1 = [u'new1', u'new2'] + slave2 = [u'default', u'default'] + >>> c.master.slave1 = [u'new1'] + Traceback (most recent call last): + [...] + tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master + >>> c.master.slave1 = [u'new1', u'new2', u'new3'] + [...] + tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master - +you have to call the `pop` function on the master:: + + >>> c.master.master = [u'oui'] + Traceback (most recent call last): + [...] + tiramisu.error.SlaveError: invalid len for the master: master which has slave1 as slave with greater len + >>> c.master.master.pop(0) + u'oui' + >>> print c.master + master = [u'non'] + slave1 = [u'new2'] + slave2 = [u'default'] + Configuration's interesting methods ------------------------------------------ diff --git a/doc/consistency.txt b/doc/consistency.txt index 47561ff..7925fa3 100644 --- a/doc/consistency.txt +++ b/doc/consistency.txt @@ -72,6 +72,96 @@ callback's action name (`hide`, `show`...), wich is a :class:`~setting.Property()`. Requirements are validated in :class:`setting.Setting`. + +Let's create an option wich has requirements:: + + >>> from tiramisu.option import * + >>> from tiramisu.config import * + >>> var2 = UnicodeOption('var2', '', u'oui') + >>> var1 = UnicodeOption('var1', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}]) + >>> var3 = UnicodeOption('var3', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}, {'option':var2, 'expected':u'non', 'action':'disabled'}]) + >>> var4 = UnicodeOption('var4', '', u'oui') + >>> od1 = OptionDescription('od1', '', [var1, var2, var3]) + >>> od2 = OptionDescription('od2', '', [var4], requires=[{'option':od1.var2, 'expected':u'oui', 'action':'hidden', 'inverse':True}]) + >>> rootod = OptionDescription('rootod', '', [od1, od2]) + >>> c = Config(rootod) + >>> c.read_write() + +The requirement here is the dict `{'option':var2, 'expected':u'non', +'action':'hidden'}` wich means that is the option `'od1.var2'` is set to +`'non'`, the option `'od1.var1'` is gonna be hidden. On the other hand, if the +option `'od1.var2'` is different from `'non'`, the option `'od1.var1'` is not +hidden any more:: + + >>> print c.cfgimpl_get_settings()[rootod.od1.var1] + [] + >>> print c.od1.var1 + value + >>> print c.od1.var2 + oui + >>> c.od1.var2 = u'non' + >>> print c.cfgimpl_get_settings()[rootod.od1.var1] + ['hidden'] + >>> print c.od1.var1 + Traceback (most recent call last): + [...] + tiramisu.error.PropertiesOptionError: trying to access to an option named: + var1 with properties ['hidden'] + >>> c.od1.var2 = u'oui' + >>> print c.cfgimpl_get_settings()[rootod.od1.var1] + [] + >>> print c.od1.var1 + value + +The requirement on `od2` is `{'option':od1.var2, 'expected':u'oui', +'action':'hidden', 'inverse':True}`, which means that if the option `od1.var2` +is set to `oui`, the option is not hidden (because of the `True` at the end of +the tuple wich means 'inverted', take a look at the :doc:`consistency` +document.):: + + >>> print c.od2.var4 + oui + >>> c.od1.var2 = u'non' + >>> print c.od2.var4 + Traceback (most recent call last): + [...] + tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden'] + >>> c.od1.var2 = u'oui' + >>> print c.od2.var4 + oui + +Requirements can be accumulated + + >>> print c.cfgimpl_get_settings()[rootod.od1.var3] + [] + >>> c.od1.var2 = u'non' + >>> print c.cfgimpl_get_settings()[rootod.od1.var3] + ['disabled', 'hidden'] + >>> c.od1.var2 = u'oui' + >>> print c.cfgimpl_get_settings()[rootod.od1.var3] + [] + +Requirements can be accumulated for different or identical properties (inverted +or not):: + + >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, + 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', + 'action':'hidden'}]) + >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, + 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', + 'action':'disabled', 'inverse':True}]) + +But it is not possible to have inverted requirements on the same property. +Here is an impossible situation:: + + >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, + 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', + 'hidden', True}]) + + Traceback (most recent call last): + [...] + ValueError: inconsistency in action types for option: var3 action: hidden + Validation upon a whole configuration object ---------------------------------------------- @@ -87,6 +177,36 @@ Other hooks are availables to validate upon a whole configuration at any time, for example the consistency between two options (typically, an :class:`IPOption` and a :class:`NetworkOption`). +Let's define validator (wich is a normal python function):: + + >>> def valid_a(value, letter=''): + ... return value.startswith(letter) + +Here is an option wich uses this validator:: + + >>> var1 = UnicodeOption('var1', '', u'oui', validator=valid_a, validator_args={'letter': 'o'}) + >>> + >>> od1 = OptionDescription('od1', '', [var1]) + >>> + >>> rootod = OptionDescription('rootod', '', [od1]) + >>> + >>> c = Config(rootod) + >>> c.read_write() + +The validation is applied at the modification time:: + + >>> c.od1.var1 = u'non' + Traceback (most recent call last): + [...] + ValueError: invalid value non for option var1 + >>> c.od1.var1 = u'oh non' + + Il est possible de désactiver la validation : + + >>> c.cfgimpl_get_settings().remove('validator') + >>> c.od1.var1 = u'non' + + Values that are calculated -------------------------------- @@ -111,3 +231,84 @@ attribute. attribute `force_store_value` enabled is considered to be modified at the first calculation +Let's create four calculation functions:: + + def return_calc(): + #return an unicode value + return u'calc' + + def return_value(value): + return value + + def return_value_param(param=u''): + return param + + def return_no_value_if_non(value): + #if value is not u'non' return value + if value == u'non': + return None + else: + return value + +Then we create four options using theses functions:: + + >>> var1 = UnicodeOption('var1', '', callback=return_calc) + >>> var2 = UnicodeOption('var2', '', callback=return_value, callback_params={'': (u'value',)}) + >>> var3 = UnicodeOption('var3', '', callback=return_value_param, callback_params={'param': (u'value_param',)}) + >>> var4 = UnicodeOption('var4', '', callback=return_no_value_if_non, callback_params={'': (('od1.var5', False),)}) + >>> var5 = UnicodeOption('var5', '', u'oui') + >>> od1 = OptionDescription('od1', '', [var1, var2, var3, var4, var5]) + >>> rootod = OptionDescription('rootod', '', [od1]) + >>> c = Config(rootod) + >>> c.read_write() + +The first option `var1` returns the result of the `return_calc` function, wich +is `u'calc'`:: + + >>> print c.od1.var1 + calc + +The second option `var2` returns the result of the `return_value` fucntion, +wich is `value`. The parameter `u'value'` is passed to this function:: + + >>> print c.od1.var2 + value + +The third option `var3` returns the result of the function `return_value_param` +with the named parameter `param` and the value `u'value_param'`:: + + >>> print c.od1.var3 + value_param + +The fourth option `var4` returns the reslut of the function `return_no_value_if_non` +that is the value of `od1.var5` exceptif the value is u`non`:: + + >>> print c.od1.var4 + oui + >>> c.od1.var5 = u'new' + >>> print c.od1.var4 + new + >>> c.od1.var5 = u'non' + >>> print c.od1.var4 + None + +The calculation replaces the default value. +If we modify the value, the calculation is not carried out any more:: + + >>> print c.od1.var1 + calc + >>> c.od1.var1 = u'new_value' + >>> print c.od1.var1 + new_value + +To force the calculation to be carried out in some cases, one must add the +`frozen` and the `force_default_on_freeze` properties:: + + >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('frozen') + >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('force_default_on_freeze') + >>> print c.od1.var1 + calc + >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('frozen') + >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('force_default_on_freeze') + >>> print c.od1.var1 + new_value