.. 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') [, ] 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:: 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.