306 lines
11 KiB
Text
306 lines
11 KiB
Text
.. default-role:: literal
|
|
|
|
.. currentmodule:: tiramisu
|
|
|
|
The global consistency
|
|
===========================
|
|
|
|
Identical option names
|
|
----------------------
|
|
|
|
If an :class:`~option.Option()` happens to be defined twice in the
|
|
:term:`schema` (e.g. the :class:`~option.OptionDescription()`),
|
|
that is the two options actually have the same name, an exception is raised.
|
|
|
|
The calculation is currently carried out in the samespace, for example
|
|
if `config.gc.name` is defined, another option in `gc` with the name
|
|
`name` is **not** allowed, whereas `config.whateverelse.name` is still
|
|
allowed.
|
|
|
|
Option's values type validation
|
|
--------------------------------
|
|
|
|
When a value is set to the option, the value is validated by the
|
|
option's :class:`option.Option()` validator's type.
|
|
|
|
Notice that if the option is `multi`, that is the `multi` attribute is set to
|
|
`True`, then the validation of the option value accepts a list of values
|
|
of the same type.
|
|
|
|
For example, an :class:`option.IntOption` validator waits for an `int` object of
|
|
course, an :class:`option.StrOption` validator waits for an `str`, vs...
|
|
|
|
Where are located the values
|
|
-------------------------------
|
|
|
|
The entry point of the acces to the values is the :class:`setting.Setting()` of
|
|
the root configuration object, but the values are actually located in the
|
|
:class:`value.Values()` object, in order to be delegated in some kind of a
|
|
`tiramisu.storage`, which can be a in-memory storage, or a persistent (for the
|
|
time being, a sqlite3) storage.
|
|
|
|
:class:`value.Values()` is also responsible of the owners and the calculation
|
|
of the options that have callbacks.
|
|
|
|
Requirements
|
|
------------
|
|
|
|
Configuration options can specify requirements as parameters at the init
|
|
time, the specification of some links between options or groups allows
|
|
to carry out a dependencies calculation. For example, an option can ben
|
|
hidden if another option has been set with some expected value. This is
|
|
just an example, the possibilities are hudge.
|
|
|
|
A requirement is a list of dictionaries that have fairly this form::
|
|
|
|
[{'option': a, 'expected': False, 'action': 'disabled', 'inverse': True,
|
|
'transitive':True, 'same_action': True}]
|
|
|
|
Actually a transformation is made to this dictionary during the validation of
|
|
this requires at the :class:`~option.Option()`'s init. The dictionary becomes
|
|
a tuple, wich is passed to the :meth:`~setting.Settings.apply_requires()`
|
|
method. Take a look at the code to fully understand the exact meaning of the
|
|
requirements:
|
|
|
|
.. automethod:: tiramisu.setting.Settings.apply_requires
|
|
|
|
|
|
The path of the option is required, the second element is the value wich is
|
|
expected to trigger the callback, it is required too, and the third one is the
|
|
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
|
|
----------------------------------------------
|
|
|
|
An option's integrity can be validated towards a whole configuration.
|
|
|
|
This type of validation is very open. Let's take a use case : an option
|
|
has a certain value, and the value of this option can change the owner
|
|
of another option or option group... Everything is possible.
|
|
|
|
.. currentmodule:: tiramisu.option
|
|
|
|
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'
|
|
|
|
You can disabled this validation::
|
|
|
|
>>> c.cfgimpl_get_settings().remove('validator')
|
|
>>> c.od1.var1 = u'non'
|
|
|
|
|
|
Values that are calculated
|
|
--------------------------------
|
|
|
|
An option that have a callback is considered to have a value that is to be
|
|
calculated.
|
|
|
|
An option's property with a `force_store_value` attribute is considered to be
|
|
modified at the first calculation.
|
|
|
|
.. automodule:: tiramisu.autolib
|
|
:members:
|
|
|
|
This is the typically protocol for accessing a option's for a calculated value,
|
|
but some twisted ways are also possible, take a look at the `force_store_value`
|
|
attribute.
|
|
|
|
.. glossary::
|
|
|
|
force store value
|
|
|
|
A calculated value (that is, an option that has a callback) with the
|
|
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
|