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