update doc
[tiramisu.git] / doc / config.txt
index 8f93a7a..3c6d9bc 100644 (file)
@@ -6,17 +6,20 @@ 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
+- :class:`tiramisu.config.Config` which is the whole configuration entry point
+
+.. image:: config.png
 
 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.
+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`
@@ -26,27 +29,29 @@ object is returned, and if no `Option` has been declared in the
 
 ::
 
+    >>> from tiramisu.config import Config 
+    >>> from tiramisu.option import BoolOption, OptionDescription
+    >>> 
     >>> gcdummy = BoolOption('dummy', 'dummy', default=False)
-    >>> gcdummy._name
-    'dummy'
-    >>> gcdummy.getdefault()
+    >>> gcdummy.impl_getdefault()
     False
-    >>> descr = OptionDescription('tiramisu', '', [gcdummy])
-    >>> cfg = Config(descr)
     >>> 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 `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``.
+The `Option` objects (in this case the :class:`~tiramisu.option.BoolOption`), 
+are organized into a tree into nested 
+:class:`~tiramisu.option.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
+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,
@@ -61,26 +66,14 @@ Let's make the protocol of accessing a config's attribute explicit
    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.
+:term:`mandatory option`. A mandatory option is an option that must have a value 
+defined.
 
-If you know the path:
+Setting the value of an option
+------------------------------
 
-::
-
-    >>> 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,
+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
 
 ::
@@ -105,18 +98,17 @@ 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:
+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])
 
@@ -137,11 +129,10 @@ None
 >>> print c.od1.var2
 value
 
-let's modify a value (careful to the value's type...)
+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
@@ -156,13 +147,382 @@ let's come back to the default value
 >>> 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`
+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()
 
-Configuration's interesting methods
+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 
+    
+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`
@@ -199,6 +559,6 @@ Here are the (useful) methods on ``Config`` (or `SubConfig`).
 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.
+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.