.. default-role:: literal

===============================
Options handling basics
===============================

Tiramisu is made of almost three main objects :

- :class:`tiramisu.option.Option` stands for the option types
- :class:`tiramisu.option.OptionDescription` is the shema, the option's structure
- :class:`tiramisu.config.Config` which is the whole configuration entry point

Accessing the `Option`'s
-------------------------

The :class:`~tiramisu.config.Config` object attribute access notation stands for 
the value of the configuration's :class:`~tiramisu.option.Option`.
:class:`~tiramisu.config.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.

::

    >>> from tiramisu.config import Config 
    >>> from tiramisu.option import BoolOption, OptionDescription
    >>> 
    >>> gcdummy = BoolOption('dummy', 'dummy', default=False)
    >>> gcdummy.impl_getdefault()
    False
    >>> cfg.dummy
    False
    >>> descr = OptionDescription('tiramisu', '', [gcdummy])
    >>> cfg = Config(descr)
    >>> cfg.dummy = True
    >>> cfg.dummy
    True
    >>> cfg.idontexist
    AttributeError: 'OptionDescription' object has no attribute 'idontexist'

The `Option` objects (in this case the :class:`~tiramisu.option.BoolOption`), 
are organized into a tree into nested 
:class:`~tiramisu.option.OptionDescription` objects. 

.. image:: config.png

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 value 
defined.

Setting the value of an option
------------------------------

An important part of the setting's configuration consists of setting the
value's option. 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 (be 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`, which 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 for option var2 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 it's 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:: be 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
~~~~~~~~~~~

.. glossary::

    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)
   default
   >>> c.od1.var1 = u'no'
   >>> print c.getowner(var1)
   user
   >>> del(c.var1)
   >>> print c.getowner(var1)
   default

You can create your own owner, for example to distinguish modification made by
one user to an other one's.

   >>> from tiramisu.setting import owners
   >>> owners.addowner('toto')
   >>> c.cfgimpl_get_settings().setowner(owners.toto)
   >>> print c.getowner(var1)
   default
   >>> c.od1.var1 = u'no'
   >>> print c.getowner(var1)
   toto
 
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', 'unknown'))
    >>> 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('unknown')
    >>> print c.od1.var3
    Traceback (most recent call last):
    tiramisu.error.PropertiesOptionError: trying to access to an option named: 
    var3 with properties ['unknown']
    >>> c.cfgimpl_get_settings().remove('unknown')
    >>> print c.od1.var3
    value 

Many properties can be defined at the same time on an option::

    >>> c.cfgimpl_get_settings().extend(['unknown1', 'unknown2'])
    
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']


.. _multi-option:
    
The multi-options
~~~~~~~~~~~~~~~~~~~~~

.. glossary:: 

    multi-option

        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()
            
A multi-option's value can be manipulated like a list::

    >>> print c.od1.var1
    [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']

But it is not possible to set a value to a multi-option which is not a list::    

    >>> c.od1.var1 = u'error'
    Traceback (most recent call last):
    ValueError: invalid value error for option var1 which must be a list
    

The master/slave groups
~~~~~~~~~~~~~~~~~~~~~~~~~


.. 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 
------------------------------------------

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 which
defines the different properties, access rules, vs... There is generally only
one Config, and many SubConfigs.