adds an extend API for the settings
[tiramisu.git] / doc / consistency.txt
1 .. default-role:: literal
2
3 .. currentmodule:: tiramisu
4
5 The global consistency
6 ===========================
7
8 Identical option names
9 ----------------------
10
11 If an :class:`~option.Option()` happens to be defined twice in the
12 :term:`schema` (e.g. the :class:`~option.OptionDescription()`),
13 that is the two options actually have the same name, an exception is raised.
14
15 The calculation is currently carried out in the samespace, for example
16 if `config.gc.name` is defined, another option in `gc` with the name
17 `name` is **not** allowed, whereas `config.whateverelse.name` is still
18 allowed.
19
20 Option's values type validation
21 --------------------------------
22
23 When a value is set to the option, the value is validated by the
24 option's :class:`option.Option()` validator's type.
25
26 Notice that if the option is `multi`, that is the `multi` attribute is set to
27 `True`, then the validation of the option value accepts a list of values
28 of the same type.
29
30 For example, an :class:`option.IntOption` validator waits for an `int` object of 
31 course, an :class:`option.StrOption` validator waits for an `str`, vs...
32
33 Where are located the values
34 -------------------------------
35
36 The entry point of the acces to the values is the :class:`setting.Setting()` of 
37 the root configuration object, but the values are actually located in the 
38 :class:`value.Values()` object, in order to be delegated in some kind of a 
39 `tiramisu.storage`, which can be a in-memory storage, or a persistent (for the 
40 time being, a sqlite3) storage.
41
42 :class:`value.Values()` is also responsible of the owners and the calculation 
43 of the options that have callbacks.
44
45 Requirements
46 ------------
47
48 Configuration options can specify requirements as parameters at the init
49 time, the specification of some links between options or groups allows
50 to carry out a dependencies calculation. For example, an option can ben
51 hidden if another option has been set with some expected value. This is
52 just an example, the possibilities are hudge.
53
54 A requirement is a list of dictionaries that have fairly this form::
55
56     [{'option': a, 'expected': False, 'action': 'disabled', 'inverse': True,
57     'transitive':True, 'same_action': True}]
58
59 Actually a transformation is made to this dictionary during the validation of 
60 this requires at the :class:`~option.Option()`'s init. The dictionary becomes 
61 a tuple, wich is passed to the :meth:`~setting.Settings.apply_requires()` 
62 method. Take a look at the code to fully understand the exact meaning of the 
63 requirements:
64
65 .. automethod:: tiramisu.setting.Settings.apply_requires
66
67
68 The path of the option is required, the second element is the value wich is 
69 expected to trigger the callback, it is required too, and the third one is the 
70 callback's action name (`hide`, `show`...), wich is a 
71 :class:`~setting.Property()`. Requirements are validated in 
72 :class:`setting.Setting`.
73
74
75 Let's create an option wich has requirements::
76
77     >>> from tiramisu.option import *
78     >>> from tiramisu.config import *
79     >>> var2 = UnicodeOption('var2', '', u'oui')
80     >>> var1 = UnicodeOption('var1', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}])
81     >>> var3 = UnicodeOption('var3', '', u'value', requires=[{'option':var2, 'expected':u'non', 'action':'hidden'}, {'option':var2, 'expected':u'non', 'action':'disabled'}])
82     >>> var4 = UnicodeOption('var4', '', u'oui')
83     >>> od1 = OptionDescription('od1', '', [var1, var2, var3])
84     >>> od2 = OptionDescription('od2', '', [var4], requires=[{'option':od1.var2, 'expected':u'oui', 'action':'hidden', 'inverse':True}])
85     >>> rootod = OptionDescription('rootod', '', [od1, od2])
86     >>> c = Config(rootod)
87     >>> c.read_write()
88
89 The requirement here is the dict `{'option':var2, 'expected':u'non', 
90 'action':'hidden'}` wich means that is the option `'od1.var2'` is set to 
91 `'non'`, the option `'od1.var1'` is gonna be hidden. On the other hand, if the 
92 option `'od1.var2'` is different from `'non'`, the option `'od1.var1'` is not 
93 hidden any more::
94
95     >>> print c.cfgimpl_get_settings()[rootod.od1.var1]
96     []
97     >>> print c.od1.var1
98     value
99     >>> print c.od1.var2
100     oui
101     >>> c.od1.var2 = u'non'
102     >>> print c.cfgimpl_get_settings()[rootod.od1.var1]
103     ['hidden']
104     >>> print c.od1.var1
105     Traceback (most recent call last):
106     tiramisu.error.PropertiesOptionError: trying to access to an option named: 
107     var1 with properties ['hidden']
108     >>> c.od1.var2 = u'oui'
109     >>> print c.cfgimpl_get_settings()[rootod.od1.var1]
110     []
111     >>> print c.od1.var1
112     value
113     
114 The requirement on `od2` is `{'option':od1.var2, 'expected':u'oui', 
115 'action':'hidden', 'inverse':True}`, which means that if the option `od1.var2` 
116 is set to `oui`, the option is not hidden (because of the `True` at the end of 
117 the tuple wich means 'inverted', take a look at the :doc:`consistency` 
118 document.)::
119
120     >>> print c.od2.var4
121     oui
122     >>> c.od1.var2 = u'non'
123     >>> print c.od2.var4
124     Traceback (most recent call last):
125     tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden']
126     >>> c.od1.var2 = u'oui'
127     >>> print c.od2.var4
128     oui
129     
130 Requirements can be accumulated 
131
132     >>> print c.cfgimpl_get_settings()[rootod.od1.var3]
133     []
134     >>> c.od1.var2 = u'non'
135     >>> print c.cfgimpl_get_settings()[rootod.od1.var3]
136     ['disabled', 'hidden']
137     >>> c.od1.var2 = u'oui'
138     >>> print c.cfgimpl_get_settings()[rootod.od1.var3]
139     []
140
141 Requirements can be accumulated for different or identical properties (inverted 
142 or not)::
143
144     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
145     ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
146     ... 'action':'hidden'}])
147     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
148     ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', 
149     ... 'action':'disabled', 'inverse':True}])
150     
151 But it is not possible to have inverted requirements on the same property. 
152 Here is an impossible situation::
153
154     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
155     ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
156     ... 'hidden', True}])
157     
158     Traceback (most recent call last):
159     ValueError: inconsistency in action types for option: var3 action: hidden
160             
161 Validation upon a whole configuration object
162 ----------------------------------------------
163
164 An option's integrity can be validated towards a whole configuration.
165
166 This type of validation is very open. Let's take a use case : an option
167 has a certain value, and the value of this option can change the owner
168 of another option or option group... Everything is possible.
169
170 .. currentmodule:: tiramisu.option
171
172 Other hooks are availables to validate upon a whole configuration at any time, 
173 for example the consistency between two options (typically, an 
174 :class:`IPOption` and a :class:`NetworkOption`).
175
176 Let's define validator (wich is a normal python function)::
177
178     >>> def valid_a(value, letter=''):
179     ...     return value.startswith(letter)
180     
181 Here is an option wich uses this validator::
182
183     >>> var1 = UnicodeOption('var1', '', u'oui', validator=valid_a, validator_args={'letter': 'o'})
184     >>> od1 = OptionDescription('od1', '', [var1])
185     >>> rootod = OptionDescription('rootod', '', [od1])
186     >>> c = Config(rootod)
187     >>> c.read_write()
188     
189 The validation is applied at the modification time::
190
191     >>> c.od1.var1 = u'non'
192     Traceback (most recent call last):
193     ValueError: invalid value non for option var1
194     >>> c.od1.var1 = u'oh non'
195     
196 You can disabled this validation::
197     
198     >>> c.cfgimpl_get_settings().remove('validator')
199     >>> c.od1.var1 = u'non'
200     
201
202 Values that are calculated
203 --------------------------------
204
205 An option that have a callback is considered to have a value that is to be 
206 calculated. 
207
208 An option's property with a `force_store_value` attribute is considered to be 
209 modified at the first calculation.
210
211 .. automodule:: tiramisu.autolib
212     :members:
213
214 This is the typically protocol for accessing a option's for a calculated value, 
215 but some twisted ways are also possible, take a look at the `force_store_value` 
216 attribute.
217
218 .. glossary:: 
219
220     force store value 
221
222         A calculated value (that is, an option that has a callback) with the 
223         attribute `force_store_value` enabled is considered to be modified at 
224         the first calculation
225
226 Let's create four calculation functions::
227
228     def return_calc():
229         #return an unicode value
230         return u'calc'
231     
232     def return_value(value):
233        return value
234     
235     def return_value_param(param=u''):
236        return param
237     
238     def return_no_value_if_non(value):
239         #if value is not u'non' return value
240         if value == u'non':
241             return None
242         else:
243             return value
244
245 Then we create four options using theses functions::
246
247     >>> var1 = UnicodeOption('var1', '', callback=return_calc)
248     >>> var2 = UnicodeOption('var2', '', callback=return_value, callback_params={'': (u'value',)})
249     >>> var3 = UnicodeOption('var3', '', callback=return_value_param, callback_params={'param': (u'value_param',)})
250     >>> var4 = UnicodeOption('var4', '', callback=return_no_value_if_non, callback_params={'': (('od1.var5', False),)})
251     >>> var5 = UnicodeOption('var5', '', u'oui')
252     >>> od1 = OptionDescription('od1', '', [var1, var2, var3, var4, var5])
253     >>> rootod = OptionDescription('rootod', '', [od1])
254     >>> c = Config(rootod)
255     >>> c.read_write()
256
257 The first option `var1` returns the result of the `return_calc` function, wich 
258 is `u'calc'`::
259
260     >>> print c.od1.var1
261     calc
262
263 The second option `var2` returns the result of the `return_value` fucntion, 
264 wich is `value`. The parameter `u'value'` is passed to this function::
265
266     >>> print c.od1.var2
267     value
268
269 The third option `var3` returns the result of the function `return_value_param`
270 with the named parameter `param` and the value `u'value_param'`::
271
272     >>> print c.od1.var3
273     value_param
274
275 The fourth option `var4` returns the reslut of the function `return_no_value_if_non` 
276 that is the value of `od1.var5` exceptif the value is  u`non`::
277
278     >>> print c.od1.var4
279     oui
280     >>> c.od1.var5 = u'new'
281     >>> print c.od1.var4
282     new
283     >>> c.od1.var5 = u'non'
284     >>> print c.od1.var4
285     None
286     
287 The calculation replaces the default value. 
288 If we modify the value, the calculation is not carried out any more::
289
290     >>> print c.od1.var1
291     calc
292     >>> c.od1.var1 = u'new_value'
293     >>> print c.od1.var1
294     new_value
295     
296 To force the calculation to be carried out in some cases, one must add the 
297 `frozen` and the `force_default_on_freeze` properties::
298
299     >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('frozen')
300     >>> c.cfgimpl_get_settings()[rootod.od1.var1].append('force_default_on_freeze')
301     >>> print c.od1.var1
302     calc
303     >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('frozen')
304     >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('force_default_on_freeze')
305     >>> print c.od1.var1
306     new_value