.. default-role:: literal =============================== Options handling basics =============================== Tiramisu is made of almost three main objects : - :class:`tiramisu.config.Config` which 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 :class:`~tiramisu.config.Config` object attribute access notation stands for the value of the configuration's :class:`~tiramisu.option.Option`. That is, the :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. 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 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') [, ] If the option name is unique, the search can be stopped once one matched option has been found: >>> print c.find_first(byname='var1') 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 ~~~~~~~~~~~ .. 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'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'] .. _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 wich 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 wich ::defines the different properties, access rules, vs... There is generally only ::one Config, and many SubConfigs.