tiramisu/doc/config.txt
2013-08-27 15:54:10 +02:00

619 lines
20 KiB
Text

.. default-role:: literal
===============================
Options handling basics
===============================
Tiramisu is made of almost three main objects :
- :class:`tiramisu.config.Config` witch is the whole configuration entry point
- :class:`tiramisu.option.Option` stands for the option types
- :class:`tiramisu.option.OptionDescription` is the shema, the option's structure
Accessing the `Option`'s
-------------------------
The `Config` object attribute access notation stands for the value of the
configuration's `Option`. That is, the `Config`'s object attribute is the name
of the `Option`, and the value is the value accessed by the `__getattr__`
attribute access mechanism.
If the attribute of the `Config` called by `__getattr__` has not been set before
(by the classic `__setattr__` mechanism), the default value of the `Option`
object is returned, and if no `Option` has been declared in the
`OptionDescription` (that is the schema of the configuration), an
`AttributeError` is raised.
::
>>> gcdummy = BoolOption('dummy', 'dummy', default=False)
>>> gcdummy._name
'dummy'
>>> gcdummy.getdefault()
False
>>> descr = OptionDescription('tiramisu', '', [gcdummy])
>>> cfg = Config(descr)
>>> cfg.dummy
False
>>> cfg.dummy = True
>>> cfg.dummy
True
>>> cfg.idontexist
AttributeError: 'OptionDescription' object has no attribute 'idontexist'
The `Option` objects (in this case the `BoolOption`), are organized into a tree
into nested `OptionDescription` objects. Every option has a name, as does every
option group. The parts of the full name of the option are separated by dots:
e.g. ``cfg.optgroup.optname``.
Let's make the protocol of accessing a config's attribute explicit
(because explicit is better than implicit):
1. If the option has not been declared, an `AttributeError` is raised,
2. If an option is declared, but neither a value nor a default value has
been set, the returned value is `None`,
3. If an option is declared and a default value has been set, but no value
has been set, the returned value is the default value of the option,
4. If an option is declared, and a value has been set, the returned value is
the value of the option.
But there are special exceptions. We will see later on that an option can be a
:term:`mandatory option`. A mandatory option is an option that must have a defined value.
If no value have been set yet, the value is `None`.
When the option is called to retrieve a value, an exception is raised.
What if a value has been set and `None` is to be returned again ? Don't
worry, an option value can be "reseted" with the help of the `option.Option.reset()`
method.
If you know the path:
::
>>> config.gc.dummy
False
Setting the values of the options
----------------------------------------
An important part of the setting of the configuration consists of setting the
values of the configuration options. There are different ways of setting values,
the first one is of course the `__setattr__` method
::
cfg.name = value
And if you wanna come back to a default value, use the builtin `del()` function::
del(cfg.name)
.. module:: tiramisu.config
.. _`tree`:
The handling of options
~~~~~~~~~~~~~~~~~~~~~~~~~~
The handling of options is split into two parts: the description of
which options are available, what their possible values and defaults are
and how they are organized into a tree. A specific choice of options is
bundled into a configuration object which has a reference to its option
description (and therefore makes sure that the configuration values
adhere to the option description).
Common manipulations
------------------------
Let's perform some common manipulation on some options:
>>> from tiramisu.config import Config
>>> from tiramisu.option import UnicodeOption, OptionDescription
>>>
>>> var1 = UnicodeOption('var1', 'first variable')
>>> var2 = UnicodeOption('var2', '', u'value')
>>>
>>> od1 = OptionDescription('od1', 'first OD', [var1, var2])
>>> rootod = OptionDescription('rootod', '', [od1])
let's set somme access rules on the main namespace
>>> c = Config(rootod)
>>> c.read_write()
let's travel the namespaces
>>> print c
[od1]
>>> print c.od1
var1 = None
var2 = value
>>> print c.od1.var1
None
>>> print c.od1.var2
value
let's modify a value (careful to the value's type...)
>>> c.od1.var1 = 'value'
Traceback (most recent call last):
[...]
ValueError: invalid value value for option var1
>>> c.od1.var1 = u'value'
>>> print c.od1.var1
value
>>> c.od1.var2 = u'value2'
>>> print c.od1.var2
value2
let's come back to the default value
>>> del(c.od1.var2)
>>> print c.od1.var2
value
The value is saved in a :class:`~tiramisu.value.Value` object. It is on this
object that we have to trigger the `reset`, wich take the option itself
(`var2`) as a parameter.
On the other side, in the `read_only` mode, it is not possible to modify the value::
>>> c.read_only()
>>> c.od1.var2 = u'value2'
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError:
cannot change the value to var2
for option ['frozen'] this option is frozen
let's retrieve the option `var1` description
>>> var1.impl_get_information('doc')
'first variable'
And if the option has been lost, it is possible to retrieve it again:
>>> c.unwrap_from_path('od1.var1').impl_get_information('doc')
'first variable'
Searching for an option
~~~~~~~~~~~~~~~~~~~~~~~~~~
In an application, knowing the path of an option is not always feasible.
That's why a tree of options can easily be searched. First, let's build such a tree::
>>> var1 = UnicodeOption('var1', '')
>>> var2 = UnicodeOption('var2', '')
>>> var3 = UnicodeOption('var3', '')
>>> od1 = OptionDescription('od1', '', [var1, var2, var3])
>>> var4 = UnicodeOption('var4', '')
>>> var5 = UnicodeOption('var5', '')
>>> var6 = UnicodeOption('var6', '')
>>> var7 = UnicodeOption('var1', '', u'value')
>>> od2 = OptionDescription('od2', '', [var4, var5, var6, var7])
>>> rootod = OptionDescription('rootod', '', [od1, od2])
>>> c = Config(rootod)
>>> c.read_write()
Second, let's find an option by his name::
>>> print c.find(byname='var1')
[<tiramisu.option.UnicodeOption object at 0x7ff1bf7d6ef0>,
<tiramisu.option.UnicodeOption object at 0x7ff1b90c7290>]
If the option name is unique, the search can be stopped once one matched option
has been found:
>>> print c.find_first(byname='var1')
<tiramisu.option.UnicodeOption object at 0x7ff1bf7d6ef0>
Instead of the option's object, the value or path can be retrieved:
>>> print c.find(byname='var1', type_='value')
[None, u'value']
>>> print c.find(byname='var1', type_='path')
['od1.var1', 'od2.var1']
Finaly, a search can be performed on the values, the type or even a combination
of all these criteria:
>>> print c.find(byvalue=u'value', type_='path')
['od2.var1']
>>> print c.find(bytype=UnicodeOption, type_='path')
['od1.var1', 'od1.var2', 'od1.var3', 'od2.var4', 'od2.var5', 'od2.var6', 'od2.var1']
>>> print c.find(byvalue=u'value', byname='var1', bytype=UnicodeOption, type_='path')
['od2.var1']
The search can be performed in a subtree:
>>> print c.od1.find(byname='var1', type_='path')
['od1.var1']
In a root tree or in a subtree, all option can be retrieved in a dict container:
>>> print c.make_dict()
{'od2.var4': None, 'od2.var5': None, 'od2.var6': None, 'od2.var1': u'value',
'od1.var1': None, 'od1.var3': None, 'od1.var2': None}
If the organisation in a tree is not important,
:meth:`~config.SubConfig.make_dict()` results can be flattened
>>> print c.make_dict(flatten=True)
{'var5': None, 'var4': None, 'var6': None, 'var1': u'value', 'var3': None,
'var2': None}
.. note:: carefull with this `flatten` parameter, here we have just lost
two options named `var1`
One can export only interesting parts of a tree of options into a dict, for
example the options that are in the same group that a given `var1` option::
>>> print c.make_dict(withoption='var1')
{'od2.var4': None, 'od2.var5': None, 'od2.var6': None, 'od2.var1': u'value',
'od1.var1': None, 'od1.var3': None, 'od1.var2': None}
>>> print c.make_dict(withoption='var1', withvalue=u'value')
{'od2.var4': None, 'od2.var5': None, 'od2.var6': None, 'od2.var1': u'value'}
and of course, :meth:`~config.SubConfig.make_dict()` can be called in a subtree:
>>> print c.od1.make_dict(withoption='var1')
{'var1': None, 'var3': None, 'var2': None}
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::
>>> 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')
default
>>> c.od1.var1 = u'non'
>>> print c.getowner('var1')
user
>>> del(c.var1)
>>> print c.getowner('var1')
default
the properties
~~~~~~~~~~~~~~~~
A property is an information on an option's state.
Let's create options with properties::
>>> var1 = UnicodeOption('var1', '', u'value', properties=('hidden',))
>>> var2 = UnicodeOption('var2', '', properties=('mandatory',))
>>> var3 = UnicodeOption('var3', '', u'value', properties=('frozen', 'inconnu'))
>>> var4 = UnicodeOption('var4', '', u'value')
>>> od1 = OptionDescription('od1', '', [var1, var2, var3])
>>> od2 = OptionDescription('od2', '', [var4], properties=('hidden',))
>>> rootod = OptionDescription('rootod', '', [od1, od2])
>>> c = Config(rootod)
>>> c.read_write()
A hidden value is a value that cannot be accessed in read/write mode. This
option cannot be modified any more. Let's try to access to an option's value
with a hidden option::
>>> print c.od1.var1
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var1
with properties ['hidden']
>>> c.read_only()
>>> print c.od1.var1
value
A mandatory option is an option with a value that shall not be `None`. The
value has to be defined. Accessing to such an option is easy in read/write
mode. But in read only mode, an error is raised if no value has been defined::
>>> c.read_write()
>>> print c.od1.var2
None
>>> c.read_only()
>>> print c.od1.var2
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var2
with properties ['mandatory']
>>> c.read_write()
>>> c.od1.var2 = u'value'
>>> c.read_only()
>>> print c.od1.var2
value
A frozen option, is an option that cannot be modified by a user.
Let's try to modify a frozen option::
>>> c.read_write()
>>> print c.od1.var3
value
>>> c.od1.var3 = u'value2'
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: cannot change the value for option var3 this option is frozen
>>> c.read_only()
>>> print c.od1.var3
value
Tiramisu allows us to use user defined properties. Let's define and use one in
read/write or read only mode::
>>> c.cfgimpl_get_settings().append('inconnu')
>>> print c.od1.var3
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named:
var3 with properties ['inconnu']
>>> c.cfgimpl_get_settings().remove('inconnu')
>>> print c.od1.var3
value
Properties can also be defined on an option group, (that is, on an
:term:`option description`), let's hide a group and try to access to it::
>>> c.read_write()
>>> print c.od2.var4
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: od2
with properties ['hidden']
>>> c.read_only()
>>> print c.od2.var4
value
Furthermore, let's retrieve the properties, delete and add the `hidden` property::
>>> c.read_write()
>>> 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.cfgimpl_get_settings()[rootod.od1.var1].remove('hidden')
>>> c.cfgimpl_get_settings()[rootod.od1.var1]
[]
>>> print c.od1.var1
value
>>> c.cfgimpl_get_settings()[rootod.od1.var1].append('hidden')
>>> 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']
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
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
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'`::
>>> 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
Configuration's interesting methods
------------------------------------------
A `Config` object is informed by an `option.OptionDescription`
instance. The attributes of the ``Config`` objects are the names of the
children of the ``OptionDescription``.
Here are the (useful) methods on ``Config`` (or `SubConfig`).
.. currentmodule:: tiramisu.config
.. class:: Config
.. autoclass:: SubConfig
:members: find, find_first, __iter__, iter_groups, iter_all, make_dict
.. automethod:: __init__
.. rubric:: Summary
.. autosummary::
find
find_first
__iter__
iter_groups
iter_all
make_dict
.. rubric:: Methods
A :class:`~config.CommonConfig` is a abstract base class. A
:class:`~config.SubConfig` is an just in time created objects that wraps an
::class:`~option.OptionDescription`. A SubConfig differs from a Config in the
::fact that a config is a root object and has an environnement, a context wich
::defines the different properties, access rules, vs... There is generally only
::one Config, and many SubConfigs.