add docstring and some docs

This commit is contained in:
gwen 2012-10-05 16:00:07 +02:00
parent 8ca58c508d
commit d3dc40033b
11 changed files with 398 additions and 547 deletions

View file

@ -7,12 +7,12 @@ Configuration Handling
:module: :api:`config.py` :module: :api:`config.py`
:tests: - :api:`test_config.py` :tests: - :api:`test_config.py`
- :api:`test_option_setting.py` - :api:`test_option_setting.py`
Main Assumption Main Assumption
=============== ===============
Configuration option objects :api:`config.Config()` are produced at the Configuration option objects :api:`config.Config()` are produced at the
entry points and handed down to where they are actually used. This keeps entry points and handed down to where they are actually used. This keeps
configuration local but available everywhere and consistent. configuration local but available everywhere and consistent.
`Config` and `Option` objects `Config` and `Option` objects
@ -35,7 +35,7 @@ very basic `Config` object manipulations:
>>> config.bool >>> config.bool
True True
Take a look at :api:`test_config.test_base_config()` or Take a look at :api:`test_config.test_base_config()` or
:api:`test_config.test_base_config_and_groups()`. :api:`test_config.test_base_config_and_groups()`.
@ -45,7 +45,7 @@ Accessing the configuration `Option`'s
The `Config` object attribute access notation stands for the value of the 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 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__` of the `Option`, and the value is the value accessed by the `__getattr__`
attribute access mechanism. attribute access mechanism.
If the attribute of the `Config` called by `__getattr__` has not been set before 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` (by the classic `__setattr__` mechanism), the default value of the `Option`
@ -70,10 +70,10 @@ object is returned, and if no `Option` has been declared in the
>>> cfg.idontexist >>> cfg.idontexist
AttributeError: 'OptionDescription' object has no attribute 'idontexist' AttributeError: 'OptionDescription' object has no attribute 'idontexist'
The configuration `Option` objects (in this case the `BoolOption`), are The configuration `Option` objects (in this case the `BoolOption`), are
organized into a tree into nested `OptionDescription` objects. Every organized into a tree into nested `OptionDescription` objects. Every
option has a name, as does every option group. The parts of the full option has a name, as does every option group. The parts of the full
name of the option are separated by dots: e.g. name of the option are separated by dots: e.g.
``config.optgroup.optname``. ``config.optgroup.optname``.
**Can you repeat it, what is the protocol of accessing a config's attribute ?** **Can you repeat it, what is the protocol of accessing a config's attribute ?**
@ -91,22 +91,21 @@ name of the option are separated by dots: e.g.
What if a value has been set and `None` is to be returned again ? Don't What if a value has been set and `None` is to be returned again ? Don't
worry, an option value can be "reseted" just by the affectation of the special worry, an option value can be "reseted" just by the affectation of the special
value `None`. An option accepts a type value as a setting, but also `None` as value `None`. An option accepts a type value as a setting, but also `None` as
a possible value. a possible value.
If you do not want to use the pythonic way, that is the attribute access If you do not want to use the pythonic way, that is the attribute access
way to obtain the value of the configuration option, you can also search way to obtain the value of the configuration option, you can also search
for it recursively in the whole config namespaces with the ``get()`` for it recursively in the whole config namespaces with the ``get()``
method : method :
:: ::
>>> config.get('bool') >>> config.get('bool')
True True
To find the right option, `get()` searches recursively into the whole To find the right option, `get()` searches recursively into the whole
tree. For example, to find an option which is in the `gc` namespace tree. For example, to find an option which is in the `gc` namespace
there are two possibilites. there are two possibilites.
If you know the path: If you know the path:
@ -128,20 +127,20 @@ Setting the values of the options
An important part of the setting of the configuration consists of setting the 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, values of the configuration options. There are different ways of setting values,
the first one is of course the `__setattr__` method the first one is of course the `__setattr__` method
:: ::
cfg.name = value cfg.name = value
wich has the same effect that the "global" `set()` method : it expects that wich has the same effect that the "global" `set()` method : it expects that
the value owner is the default :ref:`glossary#valueowner` the value owner is the default :ref:`glossary#valueowner`
:: ::
cfg.set(name=value) cfg.set(name=value)
The global `setoption()` method of the config objects can set a value with a specific owner The global `setoption()` method of the config objects can set a value with a specific owner
:: ::
@ -158,6 +157,5 @@ value has been changed and no bad side effect won't occur
>>> descr = OptionDescription('descr', '', [booloption]) >>> descr = OptionDescription('descr', '', [booloption])
>>> cfg = Config(descr) >>> cfg = Config(descr)
>>> booloption.setoption(cfg, False, 'owner') >>> booloption.setoption(cfg, False, 'owner')
>>> cfg.bool >>> cfg.bool
>>> False >>> False

View file

@ -8,29 +8,29 @@ Config API Details
- :api:`test_config_big_example.py` - :api:`test_config_big_example.py`
The handling of options is split into two parts: the description of The handling of options is split into two parts: the description of
which options are available, what their possible values and defaults are 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 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 bundled into a configuration object which has a reference to its option
description (and therefore makes sure that the configuration values description (and therefore makes sure that the configuration values
adhere to the option description). adhere to the option description).
The configuration object The configuration object
------------------------- -------------------------
:api:`config.Config()` object that lives in :api:`config.py` hold the :api:`config.Config()` object that lives in :api:`config.py` hold the
choosen values for the options (or the default value for the choosen values for the options (or the default value for the
:api:`option.Option()` object, if no choice was made). :api:`option.Option()` object, if no choice was made).
A `Config` object is informed by an :api:`option.OptionDescription` A `Config` object is informed by an :api:`option.OptionDescription`
instance. The attributes of the ``Config`` objects are the names of the instance. The attributes of the ``Config`` objects are the names of the
children of the ``OptionDescription``. children of the ``OptionDescription``.
Here are the (useful) methods on ``Config``: Here are the (useful) methods on ``Config``:
:api:`config.Config.__init__(self, descr, **overrides)`: :api:`config.Config.__init__(self, descr, **overrides)`:
``descr`` is an instance of :api:`option.OptionDescription` that ``descr`` is an instance of :api:`option.OptionDescription` that
describes the configuration object. ``override`` can be used to describes the configuration object. ``override`` can be used to
set different default values (see method ``override``). set different default values (see method ``override``).
:api:`config.Config.override(self, overrides)`: :api:`config.Config.override(self, overrides)`:
@ -38,66 +38,65 @@ Here are the (useful) methods on ``Config``:
``overrides`` is a dictionary of path strings to values. ``overrides`` is a dictionary of path strings to values.
:api:`config.Config.set(self, **kwargs)`: :api:`config.Config.set(self, **kwargs)`:
"do what I mean"-interface to option setting. Searches all paths "do what I mean"-interface to option setting. Searches all paths
starting from that config for matches of the optional arguments starting from that config for matches of the optional arguments
and sets the found option if the match is not ambiguous. and sets the found option if the match is not ambiguous.
:api:`config.Config.get(self, name)`: :api:`config.Config.get(self, name)`:
the behavior is much like the attribute access way, except that the behavior is much like the attribute access way, except that
the search for the option is performed recursively in the whole the search for the option is performed recursively in the whole
configuration tree. configuration tree.
:api:`config.Config.cfgimpl_read_write()`: :api:`config.Config.cfgimpl_read_write()`:
configuration level `read_write` status, see :doc:`status` configuration level `read_write` status, see :doc:`status`
:api:`config.Config.cfgimpl_read_only()`:
configuration level `read_only` status, see :doc:`status`
Here are some private attributes of a `Config()` object, for a :api:`config.Config.cfgimpl_read_only()`:
configuration level `read_only` status, see :doc:`status`
Here are some private attributes of a `Config()` object, for a
comprehension of the internal merchanism: comprehension of the internal merchanism:
- `_cfgimpl_descr =` :api:`option.OptionDescription()`, - `_cfgimpl_descr =` :api:`option.OptionDescription()`,
e.g. the :ref:`optionapi#schema` e.g. the :ref:`optionapi#schema`
- `_cfgimpl_values` contains the :api:`option.Option()`'s values. - `_cfgimpl_values` contains the :api:`option.Option()`'s values.
Yes, the values of the options: remember that the values are stored **inside** Yes, the values of the options: remember that the values are stored **inside**
the :api:`config.Config()` and not in the `Option()` the :api:`config.Config()` and not in the `Option()`
`_cfgimpl_values` contains something like that `_cfgimpl_values` contains something like that
:: ::
{'int': 0, 'wantframework': False, 'objspace': 'std', 'bool': False, {'int': 0, 'wantframework': False, 'objspace': 'std', 'bool': False,
'str': 'abc', 'gc': <config.Config object at 0xa33f8ec>, 'wantref': False} 'str': 'abc', 'gc': <config.Config object at 0xa33f8ec>, 'wantref': False}
We can see that values can also be config objects, it's the We can see that values can also be config objects, it's the
sub-namespaces that are stored in the values as `Config()` objects. sub-namespaces that are stored in the values as `Config()` objects.
convenience utilities (iteration, exports...) convenience utilities (iteration, exports...)
----------------------------------------------- -----------------------------------------------
With this :api:`config.Config()` configuration management entry point, With this :api:`config.Config()` configuration management entry point,
it is possible to it is possible to
- `iter` on config, notice that there is an iteration order wich is - `iter` on config, notice that there is an iteration order wich is
the order of the :ref:`optionapi#schema` specification entries, the order of the :ref:`optionapi#schema` specification entries,
- compare two configs (equality), - compare two configs (equality),
- export the whole config into a `dict` with :api:`config.make_dict()`, - export the whole config into a `dict` with :api:`config.make_dict()`,
- `validate()` an option value into a config, see :doc:`consistency`. .. - `validate()` an option value into a config, see :doc:`consistency`.
:api:`option.Option()` objects in a config are iterable in the pythonic :api:`option.Option()` objects in a config are iterable in the pythonic
way, that is something like `[(name, value) for name, value in config]`. way, that is something like `[(name, value) for name, value in config]`.
To iter on groups in the same manner, use the To iter on groups in the same manner, use the
:api:`config.Config.iter_groups()` method wich yields generators too. :api:`config.Config.iter_groups()` method wich yields generators too.
**iteration utilities** **iteration utilities**
:api:`config.Config.__iter__()` :api:`config.Config.__iter__()`
Pythonesque way of parsing group's ordered options. Pythonesque way of parsing group's ordered options.
:api:`config.Config.iter_groups(group_type=None)`: :api:`config.Config.iter_groups(group_type=None)`:
To iter on groups objects only. To iter on groups objects only.
All groups are returned if `group_type` is `None`, otherwise the groups All groups are returned if `group_type` is `None`, otherwise the groups
can be filtered by categories (families, or whatever). can be filtered by categories (families, or whatever).

View file

@ -6,42 +6,54 @@ The global configuration's consistency
:module: :api:`config.py` :module: :api:`config.py`
:tests: :api:`test_option_consistency.py` :tests: :api:`test_option_consistency.py`
Identical option names
----------------------
If an :api:`option.Option()` happens to be defined twice in the
:ref:`glossary#schema` (e.g. the :api:`option.OptionDescription()`),
that is the two options actually have the same name, an exception is raised.
The calculation is currently carried out in the samespace, for example
if `config.gc.name` is defined, another option in `gc` with the name
`name` is **not** allowed, whereas `config.whateverelse.name` is still
allowed.
Option's values type validation Option's values type validation
-------------------------------- --------------------------------
When a value is set to the option, the value is validated by the When a value is set to the option, the value is validated by the
option's :api:`option.Option()` validator's type. option's :api:`option.Option()` validator's type.
Notice that if the option is `multi`, that is the `multi` attribute is set to Notice that if the option is `multi`, that is the `multi` attribute is set to
`True`, then the validation of the option value accepts a list of values `True`, then the validation of the option value accepts a list of values
of the same type. of the same type.
Requirements Requirements
------------ ------------
Configuration options can specify requirements as parameters at the init Configuration options can specify requirements as parameters at the init
time, the specification of some links between options or groups allows time, the specification of some links between options or groups allows
to carry out a dependencies calculation. For example, an option can ben to carry out a dependencies calculation. For example, an option can ben
hidden if another option has been set with some expected value. This is hidden if another option has been set with some expected value. This is
just an example, because the possibilities are hudge. just an example, because the possibilities are hudge.
A requirement is specified using a list of triplets. The first element A requirement is specified using a list of triplets. The first element
of the triplet gives the path of the option that is required, the second of the triplet gives the path of the option that is required, the second
element is the value wich is expected to trigger the callback, and the element is the value wich is expected to trigger the callback, and the
third one is the callback's action name (`hide`, `show`...):: third one is the callback's action name (`hide`, `show`...)::
stroption = StrOption('str', 'Test string option', default="abc", stroption = StrOption('str', 'Test string option', default="abc",
requires=[('int', 1, 'hide')]) requires=[('int', 1, 'hide')])
Take a look at an example here Take a look at an example here
:api:`test_option_consistency.test_hidden_if_in()` :api:`test_option_consistency.test_hidden_if_in()`
Config updates Config updates
--------------- ---------------
New configuration options and groups can be dynamically added. New configuration options and groups can be dynamically added.
The configuration has to be *updated* after that the description has been The configuration has to be *updated* after that the description has been
passed to the Config objet, see: passed to the Config objet, see:
:: ::
@ -53,50 +65,25 @@ passed to the Config objet, see:
>>> config.newoption >>> config.newoption
False False
in in
- :api:`test_option_consistency.test_newoption_add_in_descr()` - :api:`test_option_consistency.test_newoption_add_in_descr()`
- :api:`test_option_consistency.test_newoption_add_in_subdescr()` - :api:`test_option_consistency.test_newoption_add_in_subdescr()`
- :api:`test_option_consistency.test_newoption_add_in_config()` - :api:`test_option_consistency.test_newoption_add_in_config()`
Validation upon a whole configuration object Validation upon a whole configuration object
---------------------------------------------- ----------------------------------------------
An option's integrity can be validated towards a whole configuration. An option's integrity can be validated towards a whole configuration.
This type of validation is very open. Let's take a use case : an option This type of validation is very open. Let's take a use case : an option
has a certain value, and the value of this option can change the owner has a certain value, and the value of this option can change the owner
of another option or option group... Everything is possible. of another option or option group... Everything is possible.
For example, the configuration paths have to be unique in the For example, the configuration paths have to be unique in the
:ref:`glossary#schema`, the validation is carried out at the :ref:`glossary#schema`, the validation is carried out at the
:api:`config.Config._cfgimpl_build()` time in the :api:`config.Config._cfgimpl_build()` time in the
:api:`config.Config._validate_duplicates()` method. :api:`config.Config._validate_duplicates()` method.
Other hook are availables to validate upon a whole configuration at any Other hook are availables to validate upon a whole configuration at any
time. time.
Identical option names
----------------------
If an :api:`option.Option()` happens to be defined twice in the
:ref:`glossary#schema` (e.g. the :api:`option.OptionDescription()`),
that is the two options actually have the same name, an exception is raised.
The calculation is currently carried out in the samespace, for example
if `config.gc.name` is defined, another option in `gc` with the name
`name` is **not** allowed, whereas `config.whateverelse.name` is still
allowed.
.. the calculation was carried out by the requires, wich is not a goog idead
Type constraints with the `multi` type
----------------------------------------
By convention, if a multi option has somme requires, the constraints on
the multi type is in all the OptionGroup (a group has to be `multi`, and
a multi of the same length).
See :api:`test_option_consistency.test_multi_constraints()`

View file

@ -1,70 +0,0 @@
- abstract values from `gaspacho`
Les types possibles :
- sans valeur : `boolean`
- avec valeur : `unicode` (un texte libre), `integer` (un chiffre), `enum` (une liste de choix prédéfinies) et `list` (une liste de choix libres).
Les types sans valeurs sont les plus simples. Par exemple cette règle nattend
aucune valeur particulière Vérifier que Firefox est le navigateur par défaut.
Alors que celle-ci attend une adresse IP Configuration du serveur proxy manuelle.
Il existe un autre type (multi) qui permet de mêler plusieurs types.
Il sagit bien de définir ici le type de la règle (et uniquement de la règle).
- configuration levels in `creole`
*thu, 28 april 2011*
Exemple de niveau de configuration (dans l'ordre) :
1. - Coeur
2.
- Coeur
- gen_config
3.
- Coeur
- gen_config
- EAD
4.
- Coeur
- EAD
5.
- Coeur
- baculaconfig.py
(`fill` : calcule une valeur jusqu'à ce que l'utilisateur change la
valeur)
Gestion des ACL en écriture :
Le coeur charge les variables
- si auto : seul le coeur peut la modifier (cas 1) ;
- si fill : le coeur calcule une valeur tant que pas configuré par
l'utilisateur. L'utilisateur peut modifier (cas 2 ou 3) ;
- des variables modifiables que par gen_config (cas 2) ;
- des variables modifiables par gen_config ou l'EAD (cas 3) ;
- des variables d'autres applications (cas 4 et 5).
Gestion des ACLs en lecture :
- seule une application peut lire certaines variables (exemple un mot de
passe).

View file

@ -57,7 +57,7 @@ glossary
**forced on freeze** **forced on freeze**
A single option is frozen and we want the option to return something A single option is frozen and we want the option to return something
else than his value, for example his default value, see else than his value, typically his default value, see
:ref:`status#frozen` :ref:`status#frozen`
.. _`valueowner`: .. _`valueowner`:
@ -83,16 +83,6 @@ glossary
a disabled option has a different behaviour on regards to the access a disabled option has a different behaviour on regards to the access
of the value in the configuration, see :doc:`status` for more details. of the value in the configuration, see :doc:`status` for more details.
**fill option**
a fill option is like an automatic option except that it is
calculated only if a value hasn't been set.
**auto option**
an automatic option is an option thas is carried out by an external
calculation
.. _mandatory: .. _mandatory:
**mandatory option** **mandatory option**
@ -101,7 +91,6 @@ glossary
set, that is the default value cannot be `None`, see set, that is the default value cannot be `None`, see
:ref:`optionapi#optioninit` :ref:`optionapi#optioninit`
.. _consistency: .. _consistency:
**consistency** **consistency**

View file

@ -10,14 +10,14 @@ Options API Details
Description of Options Description of Options
---------------------- ----------------------
All the constructors take a ``name`` and a ``doc`` argument as first All the constructors take a ``name`` and a ``doc`` argument as first
arguments to give the option or option group a name and to document it. arguments to give the option or option group a name and to document it.
Most constructors take a ``default`` argument that specifies the default Most constructors take a ``default`` argument that specifies the default
value of the option. If this argument is not supplied the default value value of the option. If this argument is not supplied the default value
is assumed to be ``None``. is assumed to be ``None``.
Appart from that, the `Option` object is not supposed to contain any Appart from that, the `Option` object is not supposed to contain any
other value than the `tainted` attribute, which is explained later. The other value than the `tainted` attribute, which is explained later. The
container of the value is in the `Config` object. container of the value is in the `Config` object.
``OptionDescription`` ``OptionDescription``
@ -30,12 +30,12 @@ This class is used to group suboptions.
``OptionDescription`` instances for nested namespaces). ``OptionDescription`` instances for nested namespaces).
``set_group_type(self, group_name)`` ``set_group_type(self, group_name)``
Three available group_types : `default`, `family`, `group` and Three available group_types : `default`, `family`, `group` and
`master` (for master~slave group type). Notice that for a `master` (for master~slave group type). Notice that for a
master~slave group, the name of the group and the name of the master~slave group, the name of the group and the name of the
master option are identical. master option are identical.
`Options description` objects lives in the `_cfgimpl_descr` config attribute. `Options description` objects lives in the `_cfgimpl_descr` config attribute.
If you need to access an option object, you can do it with the OptionDescription If you need to access an option object, you can do it with the OptionDescription
object. Not only the value of the option by attribute access, but the option object. Not only the value of the option by attribute access, but the option
@ -45,7 +45,7 @@ option named `name` in a `gc` group the `name` object can be accessed like
this:: this::
conf._cfgimpl_descr.name conf._cfgimpl_descr.name
of sub configs with :: of sub configs with ::
conf.gc._cfgimpl_descr.name conf.gc._cfgimpl_descr.name
@ -53,7 +53,7 @@ of sub configs with ::
This is a binding. The option objects are in the `_children` config's attribute. This is a binding. The option objects are in the `_children` config's attribute.
Why accessing an option object ? It is possible for example freeze the Why accessing an option object ? It is possible for example freeze the
configuration option configuration option
:: ::
@ -67,7 +67,7 @@ generic option ``__init__`` method:
``__init__(name, doc, default=None, requires=None, multi=False, mandatory=False)`` ``__init__(name, doc, default=None, requires=None, multi=False, mandatory=False)``
:``default``: specifies the default value of the option. :``default``: specifies the default value of the option.
:``requires``: is a list of names of options located anywhere in the configuration. :``requires``: is a list of names of options located anywhere in the configuration.
:``multi``: means the value can be a list. :``multi``: means the value can be a list.
:``mandatory``: see :ref:`glossary#mandatory`. :``mandatory``: see :ref:`glossary#mandatory`.
@ -77,7 +77,7 @@ generic option ``__init__`` method:
``BoolOption`` ``BoolOption``
++++++++++++++ ++++++++++++++
Represents a choice between ``True`` and ``False``. Represents a choice between ``True`` and ``False``.
``IntOption`` ``IntOption``
+++++++++++++ +++++++++++++
@ -103,8 +103,8 @@ Redirects to another configuration option in the configuration, that is :
- can set the value of the target too. - can set the value of the target too.
``__init__(self, name, path)`` ``__init__(self, name, path)``
`path` is the path to the target, the option `path` is the path to the target, the option
``IPOption`` ``IPOption``
+++++++++++++ +++++++++++++
@ -124,4 +124,3 @@ Represents a choice out of several objects. The option can also have the value
``__init__(self, name, doc, values, default=None, requires=None)`` ``__init__(self, name, doc, values, default=None, requires=None)``
``values`` is a list of values the option can possibly take. ``values`` is a list of values the option can possibly take.

View file

@ -1,82 +0,0 @@
#!/usr/bin/python
# unproudly borrowed from David Goodger's rst2html.py
"""
A minimal front end to the Docutils Publisher, producing HTML.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except:
pass
from docutils.core import publish_cmdline, default_description
# ____________________________________________________________
from docutils import nodes, utils
from docutils.parsers.rst import roles
from docutils.writers import manpage
"""
description of the new roles:
`:api:` : link to the code
- code.py becomes api/code.html
- code.Code.code_test becomes api/code.Code.code_test.html
- code.Code() becomes api/code.Code.html
`:doc:`a link to an internal file
example become example.html
ref: link with anchor as in an external file
:ref:`toto#titi` becomes toto.html#titi
"""
from os.path import splitext
def api_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
basename = text
if "(" in text:
basename = text.split("(")[0]
if ".py" in text:
basename = splitext(text)[0]
if "test_" in text:
refuri = "api/" + "tiramisu.test." + basename + '.html'
else:
refuri = "api/" + "tiramisu." + basename + '.html'
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(text), refuri=refuri,
**options)
return [node], []
roles.register_local_role('api', api_reference_role)
def doc_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
refuri = text + '.html'
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(text), refuri=refuri,
**options)
return [node], []
roles.register_local_role('doc', doc_reference_role)
def ref_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
fname, anchor = text.split('#')
refuri = fname + '.html#' + anchor
roles.set_classes(options)
node = nodes.reference(rawtext, utils.unescape(anchor), refuri=refuri,
**options)
return [node], []
roles.register_local_role('ref', ref_reference_role)
# ____________________________________________________________
description = ("Generates plain unix manual documents. " + default_description)
publish_cmdline(writer=manpage.Writer(), description=description)

View file

@ -5,13 +5,13 @@ Configuration status
:module: :api:`config.py` :module: :api:`config.py`
:tests: - :api:`test_option_owner.py` :tests: - :api:`test_option_owner.py`
- :api:`test_option_type.py` - :api:`test_option_type.py`
- :api:`test_option_default.py` - :api:`test_option_default.py`
Available configuration statuses Available configuration statuses
---------------------------------- ----------------------------------
These configuration statuses corresponds to specific global attributes : These configuration statuses corresponds to specific global attributes :
**read write status** **read write status**
@ -21,15 +21,15 @@ These configuration statuses corresponds to specific global attributes :
To enable read-write status, call To enable read-write status, call
:api:`config.Config.cfgimpl_read_write()` :api:`config.Config.cfgimpl_read_write()`
**read only status** **read only status**
The whole configuration is `frozen`, that is modifiying a value is The whole configuration is `frozen`, that is modifiying a value is
forbidden. We can access to a configuration option only with the forbidden. We can access to a configuration option only with the
`__getattr__` property. `__getattr__` property.
The configuration has not an access to the hidden options The configuration has not an access to the hidden options
but can read the disabled options. but can read the disabled options.
To enable read only status, call :api:`config.Config.cfgimpl_read_only()` To enable read only status, call :api:`config.Config.cfgimpl_read_only()`
@ -44,37 +44,38 @@ These configuration statuses corresponds to specific global attributes :
Freezing a configuration Freezing a configuration
--------------------------- ---------------------------
At the configuration level, :api:`config.Config.cfgimpl_freeze()` freezes At the configuration level, :api:`config.Config.cfgimpl_freeze()` freezes
the whole configuration options. the whole configuration options.
- :api:`test_option_type.test_frozen_value()` - :api:`test_option_type.test_frozen_value()`
- :api:`test_option_type.test_freeze()` - :api:`test_option_type.test_freeze()`
.. _`frozen`: .. _`frozen`:
It is possible to *freeze* a single `Option` object with It is possible to *freeze* a single `Option` object with
:api:`option.Option.freeze()`. If you try to modify a frozen option, it :api:`option.Option.freeze()`. If you try to modify a frozen option, it
raises a `TypeError: trying to change a frozen option object`. raises a `TypeError: trying to change a frozen option object`.
- :api:`test_option_type.test_freeze_one_option()` - :api:`test_option_type.test_freeze_one_option()`
Moreover, frozen option can return his default value if Moreover, frozen option can return his default value if
:api:`option.Option.force_default()` has been called on this option, :api:`option.Option.force_default()` has been called on this option,
see :api:`test_option_default.test_force_default_on_freeze()` see :api:`test_option_default.test_force_default_on_freeze()`
Restricted access to an `Option()` Restricted access to an `Option()`
----------------------------------- -----------------------------------
Configuration options access statuses are defined at configuration level Configuration options access statuses are defined at configuration level
that corresponds to the :api:`option.Option()`'s `properties` attribute. that corresponds to the :api:`option.Option()`'s `properties` attribute,
for example
**hidden** **hidden**
This means that an option raises an `HiddenOptionError` if we try to access This means that an option raises an error if we try to access
the value of the option. the value of the option.
See `hide()` or `show()` in `Option()` that comes from See `hide()` or `show()` in `Option()` that comes from
:api:`option.HiddenBaseType` :api:`option.HiddenBaseType`
corresponding convenience API provided: corresponding convenience API provided:
@ -87,10 +88,10 @@ corresponding convenience API provided:
**disabled** **disabled**
This means that an option *doesn't exists* (doesn't say anything This means that an option *doesn't exists* (doesn't say anything
much more thant an `AttibuteAccess` error) much more thant an `AttibuteAccess` error)
See in :api:`option.DisabledBaseType` the origins of See in :api:`option.DisabledBaseType` the origins of
`Option.enable()` or `Option.disable()` `Option.enable()` or `Option.disable()`
corresponding convenience API provided: corresponding convenience API provided:
@ -104,18 +105,18 @@ corresponding convenience API provided:
Value owners Value owners
------------- -------------
Every configuration option has a **owner**. When the option is Every configuration option has a **owner**. When the option is
instanciated, the owner is `default` because a default value has been instanciated, the owner is `default` because a default value has been
set (including `None`, take a look at the tests). set (including `None`, take a look at the tests).
The `value_owner` is the man who did it. Yes, the man who changed the value of the The `value_owner` is the man who did it. Yes, the man who changed the value of the
configuration option. configuration option.
- At the instance of the `Config` object, the value owner is `default` because - At the instance of the `Config` object, the value owner is `default` because
the default values are set at the instance of the configuration option object, the default values are set at the instance of the configuration option object,
:: ::
# let's expect there is an option named 'name' # let's expect there is an option named 'name'
config = Config(descr, bool=False) config = Config(descr, bool=False)
# the override method has been called # the override method has been called
@ -143,29 +144,30 @@ configuration option.
Special behaviors for an option Special behaviors for an option
--------------------------------- ---------------------------------
**auto** **mandatory**
This means that it is a calculated value and therefore automatically A mandatory option shall return a value. If a value, or a default value
has not been set, a error is raised.
**has a callback**
This means that it is a calculated value and therefore automatically
protected it cannot be modified by attribute access. protected it cannot be modified by attribute access.
Its inner state is represented by :api:`option.Option.has_callback()` Its inner state is represented by :api:`option.Option.has_callback()`
and :api:`option.Option.hascallback_and_isfrozen()` and :api:`option.Option.hascallback_and_isfrozen()`
**fill** **force default**
if the configuration option has a default value, the default is if the configuration option has a default value, the default is
returned, otherwise the value is calculated. returned, otherwise the value is calculated.
Its inner state is represented by :api:`option.Option.has_callback()` Its inner state is represented by :api:`option.Option.force_default()`
`default` value owner Configuration options have default values that are stored in the
---------------------- `Option()` object itself. Default values, the `default`, can be set in
Configuration options have default values that are stored in the
`Option()` object itself. Default values, the `default`, can be set in
various ways. various ways.
If a default value is modified by overriding it, not only the value of If a default value is modified by overriding it, not only the value of
the option resets to the default that is proposed, but the owner is the option resets to the default that is proposed, but the owner is
modified too, it is reseted to `default`. modified too, it is reseted to `default`.

View file

@ -45,40 +45,22 @@ alors que gc est un groupe
:date: 12 avril :date: 12 avril
- faire un mode dégradé avec des warnings
- validations de longueur des maitres/esclaves ailleurs à sortir des requires - validations de longueur des maitres/esclaves ailleurs à sortir des requires
et à mettre dans des validators et à mettre dans des validators
:date: 3 avril 2012 :date: 3 avril 2012
- hide sur les sous-sous groupe : il faut que ça hide **tout** les sous-groupe
récursivement
groupes `master/slaves`:
faut-il coder les multi avec des requires, ou bien simplement
un groupe avec comme variable le nom du groupe ?
auto, fill, obligatoire
2012-03-22 2012-03-22
**groupe master** **groupe master**
faire une api du genre : `Option().is_master()` faire une api du genre : `Option().is_master()`
pour cela, tester `if self.parent._name == self._name: return True` pour cela, tester `if self.parent._name == self._name: return True`
- mettre un attribut `auto` aux options de configuration, de manière à
ce qu'elles sachent quelle fonction eos appeler (que ça soit une info
dans l'option ou bien au niveau de la config ?)
le fait de détecter un "auto" vient du owner, mais il faut savoir
quelle fonction appeler
A documenter A documenter
------------- -------------
- les variables multiples - les variables multiples
- expliquer les urls du json dans la doc
- documenter le typage des options descriptions descr_type - documenter le typage des options descriptions descr_type
A ajouter A ajouter
@ -87,40 +69,5 @@ A ajouter
Option -> attribut help (en plus de doc) Option -> attribut help (en plus de doc)
get_help() (à mettre en class Type avec Doc aussi) get_help() (à mettre en class Type avec Doc aussi)
separator -> pas pour l'instant
fill, auto, obligatoire
nouveau type :
type option (dérivé de ChoiceOPtion) dans lequel il y a des nouvelles valeurs
possibles (pas de validations) ou plutôt une StringOption qui propose un choix
de valeurs par défault de type liste.
:date: 24 mars
- hide pour les sous-sous config (récursivement) et pas seulement une
seule sous-config (ou bien, quelque chose de réglable)
- validate global : vérifier à l'init de la conf qu'une variable
n'existe pas déjà, etc
:date: 26 janvier
- un attribut eosfunc pour auto + les paramètres à donner à la fonction
pareil pour le fill (function et paramètres)
reset
-------
**à discuter** : ça correspond exactement au override,
ou bien au opt.setoption(None, 'default')
**si la valeur par défaut est définie, un __get__ ne pourra jamais
renvoyer None.** ce qui est bloquant. Il faut pouvoir revenir à None.
pour supprimer la valeur d'une options (et revenir à la valeur par défault)
cfg.reset() (supprime _cfgimpl_value[name]) et _cfgimpl_value_owner[name])
reset()

View file

@ -16,15 +16,15 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# The original `Config` design model is unproudly borrowed from # The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from copy import copy from copy import copy
from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError, from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
MandatoryError, MethodCallError) MandatoryError, MethodCallError)
from tiramisu.option import (OptionDescription, Option, SymLinkOption, from tiramisu.option import (OptionDescription, Option, SymLinkOption,
group_types, Multi, apply_requires) group_types, Multi, apply_requires)
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
@ -33,63 +33,59 @@ from tiramisu.autolib import carry_out_calculation
default_owner = 'user' default_owner = 'user'
# ____________________________________________________________ # ____________________________________________________________
class Config(object): class Config(object):
"properties attribute: the name of a property enables this property"
_cfgimpl_properties = ['hidden', 'disabled'] _cfgimpl_properties = ['hidden', 'disabled']
"mandatory means: a mandatory option has to have a value that is not None"
_cfgimpl_mandatory = True _cfgimpl_mandatory = True
_cfgimpl_frozen = True _cfgimpl_frozen = True
_cfgimpl_owner = default_owner _cfgimpl_owner = default_owner
_cfgimpl_toplevel = None _cfgimpl_toplevel = None
# TODO implement unicity by name
# _cfgimpl_unique_names = True
def __init__(self, descr, parent=None, **overrides): def __init__(self, descr, parent=None, **overrides):
""" Configuration option management master class
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
:param overrides: can be used to set different default values
(see method ``override``)
:param parent: is None if the ``Config`` is root parent Config otherwise
:type parent: ``Config``
"""
self._cfgimpl_descr = descr self._cfgimpl_descr = descr
self._cfgimpl_value_owners = {} self._cfgimpl_value_owners = {}
self._cfgimpl_parent = parent self._cfgimpl_parent = parent
# `Config()` indeed takes care of the `Option()`'s values "`Config()` indeed is in charge of the `Option()`'s values"
self._cfgimpl_values = {} self._cfgimpl_values = {}
self._cfgimpl_previous_values = {} self._cfgimpl_previous_values = {}
# XXX warnings are a great idea, let's make up a better use of it "warnings are a great idea, let's make up a better use of it"
self._cfgimpl_warnings = [] self._cfgimpl_warnings = []
self._cfgimpl_toplevel = self._cfgimpl_get_toplevel() self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
# `freeze()` allows us to carry out this calculation again if necessary '`freeze()` allows us to carry out this calculation again if necessary'
self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen
self._cfgimpl_build(overrides) self._cfgimpl_build(overrides)
def _validate_duplicates(self, children): def _validate_duplicates(self, children):
"""duplicates Option names in the schema
:type children: list of `Option` or `OptionDescription`
"""
duplicates = [] duplicates = []
for dup in children: for dup in children:
if dup._name not in duplicates: if dup._name not in duplicates:
duplicates.append(dup._name) duplicates.append(dup._name)
else: else:
raise ConflictConfigError('duplicate option name: ' raise ConflictConfigError('duplicate option name: '
'{0}'.format(dup._name)) '{0}'.format(dup._name))
# TODO implement unicity by name
# def _validate_duplicates_for_names(self, children):
# "validates duplicates names agains the whole config"
# rootconfig = self._cfgimpl_get_toplevel()
# if self._cfgimpl_unique_names:
# for dup in children:
# try:
# print dup._name
# try:
# print rootconfig.get(dup._name)
# except AttributeError:
# pass
# raise NotFoundError
# #rootconfig.get(dup._name)
# except NotFoundError:
# pass # no identical names, it's fine
# else:
# raise ConflictConfigError('duplicate option name: '
# '{0}'.format(dup._name))
def _cfgimpl_build(self, overrides): def _cfgimpl_build(self, overrides):
"""
- builds the config object from the schema
- settles various default values for options
:param overrides: dict of options name:default values
"""
self._validate_duplicates(self._cfgimpl_descr._children) self._validate_duplicates(self._cfgimpl_descr._children)
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, Option): if isinstance(child, Option):
if child.is_multi(): if child.is_multi():
childdef = Multi(copy(child.getdefault()), config=self, childdef = Multi(copy(child.getdefault()), config=self,
child=child) child=child)
self._cfgimpl_values[child._name] = childdef self._cfgimpl_values[child._name] = childdef
self._cfgimpl_previous_values[child._name] = list(childdef) self._cfgimpl_previous_values[child._name] = list(childdef)
@ -98,7 +94,7 @@ class Config(object):
else: else:
childdef = child.getdefault() childdef = child.getdefault()
self._cfgimpl_values[child._name] = childdef self._cfgimpl_values[child._name] = childdef
self._cfgimpl_previous_values[child._name] = childdef self._cfgimpl_previous_values[child._name] = childdef
self._cfgimpl_value_owners[child._name] = 'default' self._cfgimpl_value_owners[child._name] = 'default'
elif isinstance(child, OptionDescription): elif isinstance(child, OptionDescription):
self._validate_duplicates(child._children) self._validate_duplicates(child._children)
@ -106,10 +102,10 @@ class Config(object):
self.override(overrides) self.override(overrides)
def cfgimpl_update(self): def cfgimpl_update(self):
"dynamically adds `Option()` or `OptionDescription()`" """dynamically adds `Option()` or `OptionDescription()`
# Nothing is static. Everything evolve. """
# FIXME this is an update for new options in the schema only # FIXME this is an update for new options in the schema only
# see the update_child() method of the descr object # see the update_child() method of the descr object
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, Option): if isinstance(child, Option):
if child._name not in self._cfgimpl_values: if child._name not in self._cfgimpl_values:
@ -126,45 +122,50 @@ class Config(object):
self._cfgimpl_values[child._name] = Config(child, parent=self) self._cfgimpl_values[child._name] = Config(child, parent=self)
def override(self, overrides): def override(self, overrides):
"""
overrides default values. This marks the overridden values as defaults.
:param overrides: is a dictionary of path strings to values.
"""
for name, value in overrides.iteritems(): for name, value in overrides.iteritems():
homeconfig, name = self._cfgimpl_get_home_by_path(name) homeconfig, name = self._cfgimpl_get_home_by_path(name)
homeconfig.setoption(name, value, 'default') homeconfig.setoption(name, value, 'default')
def cfgimpl_set_owner(self, owner): def cfgimpl_set_owner(self, owner):
":param owner: sets the default value for owner at the Config level"
self._cfgimpl_owner = owner self._cfgimpl_owner = owner
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
self._cfgimpl_values[child._name].cfgimpl_set_owner(owner) self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
# ____________________________________________________________ # ____________________________________________________________
# properties methods
def _cfgimpl_has_properties(self): def _cfgimpl_has_properties(self):
"has properties means the Config's properties attribute is not empty"
return bool(len(self._cfgimpl_properties)) return bool(len(self._cfgimpl_properties))
def _cfgimpl_has_property(self, propname): def _cfgimpl_has_property(self, propname):
"""has property propname in the Config's properties attribute
:param property: string wich is the name of the property"""
return propname in self._cfgimpl_properties return propname in self._cfgimpl_properties
def cfgimpl_enable_property(self, propname): def cfgimpl_enable_property(self, propname):
"puts property propname in the Config's properties attribute"
if self._cfgimpl_parent != None: if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be" raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object") "used with non-root Config() object")
if propname not in self._cfgimpl_properties: if propname not in self._cfgimpl_properties:
self._cfgimpl_properties.append(propname) self._cfgimpl_properties.append(propname)
def cfgimpl_disable_property(self, propname): def cfgimpl_disable_property(self, propname):
"deletes property propname in the Config's properties attribute"
if self._cfgimpl_parent != None: if self._cfgimpl_parent != None:
raise MethodCallError("this method root_hide() shall not be" raise MethodCallError("this method root_hide() shall not be"
"used with non-root Config() object") "used with non-root Config() object")
if self._cfgimpl_has_property(propname): if self._cfgimpl_has_property(propname):
self._cfgimpl_properties.remove(propname) self._cfgimpl_properties.remove(propname)
def cfgimpl_non_mandatory(self):
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory machin() shall not be"
"used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = False
# ____________________________________________________________ # ____________________________________________________________
# attribute methods
def __setattr__(self, name, value): def __setattr__(self, name, value):
"attribute notation mechanism for the setting of the value of an option"
if name.startswith('_cfgimpl_'): if name.startswith('_cfgimpl_'):
self.__dict__[name] = value self.__dict__[name] = value
return return
@ -174,9 +175,10 @@ class Config(object):
if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption: if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
self._validate(name, getattr(self._cfgimpl_descr, name)) self._validate(name, getattr(self._cfgimpl_descr, name))
self.setoption(name, value, self._cfgimpl_owner) self.setoption(name, value, self._cfgimpl_owner)
def _validate(self, name, opt_or_descr): def _validate(self, name, opt_or_descr):
apply_requires(opt_or_descr, self) "validation for the setattr and the getattr"
apply_requires(opt_or_descr, self)
if not isinstance(opt_or_descr, Option) and \ if not isinstance(opt_or_descr, Option) and \
not isinstance(opt_or_descr, OptionDescription): not isinstance(opt_or_descr, OptionDescription):
raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr))) raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
@ -187,10 +189,11 @@ class Config(object):
if properties != []: if properties != []:
raise PropertiesOptionError("trying to access" raise PropertiesOptionError("trying to access"
" to an option named: {0} with properties" " to an option named: {0} with properties"
" {1}".format(name, str(properties)), " {1}".format(name, str(properties)),
properties) properties)
def _is_empty(self, opt): def _is_empty(self, opt):
"convenience method to know if an option is empty"
if (not opt.is_multi() and self._cfgimpl_values[opt._name] == None) or \ if (not opt.is_multi() and self._cfgimpl_values[opt._name] == None) or \
(opt.is_multi() and (self._cfgimpl_values[opt._name] == [] or \ (opt.is_multi() and (self._cfgimpl_values[opt._name] == [] or \
None in self._cfgimpl_values[opt._name])): None in self._cfgimpl_values[opt._name])):
@ -198,13 +201,14 @@ class Config(object):
return False return False
def __getattr__(self, name): def __getattr__(self, name):
# attribute access by passing a path, "attribute notation mechanism for accessing the value of an option"
# for instance getattr(self, "creole.general.family.adresse_ip_eth0") # attribute access by passing a path,
# for instance getattr(self, "creole.general.family.adresse_ip_eth0")
if '.' in name: if '.' in name:
homeconfig, name = self._cfgimpl_get_home_by_path(name) homeconfig, name = self._cfgimpl_get_home_by_path(name)
return getattr(homeconfig, name) return getattr(homeconfig, name)
opt_or_descr = getattr(self._cfgimpl_descr, name) opt_or_descr = getattr(self._cfgimpl_descr, name)
# symlink options # symlink options
if type(opt_or_descr) == SymLinkOption: if type(opt_or_descr) == SymLinkOption:
return getattr(self, opt_or_descr.path) return getattr(self, opt_or_descr.path)
if name not in self._cfgimpl_values: if name not in self._cfgimpl_values:
@ -218,7 +222,7 @@ class Config(object):
raise AttributeError("%s object has no attribute %s" % raise AttributeError("%s object has no attribute %s" %
(self.__class__, name)) (self.__class__, name))
if not isinstance(opt_or_descr, OptionDescription): if not isinstance(opt_or_descr, OptionDescription):
# options with callbacks (fill or auto) # options with callbacks (fill or auto)
if opt_or_descr.has_callback(): if opt_or_descr.has_callback():
value = self._cfgimpl_values[name] value = self._cfgimpl_values[name]
if (not opt_or_descr.is_frozen() or \ if (not opt_or_descr.is_frozen() or \
@ -228,11 +232,11 @@ class Config(object):
return value return value
else: else:
return value return value
result = carry_out_calculation(name, result = carry_out_calculation(name,
callback=opt_or_descr.getcallback(), callback=opt_or_descr.getcallback(),
callback_params=opt_or_descr.getcallback_params(), callback_params=opt_or_descr.getcallback_params(),
config=self._cfgimpl_get_toplevel()) config=self._cfgimpl_get_toplevel())
# this result **shall not** be a list # this result **shall not** be a list
# for example, [1, 2, 3, None] -> [1, 2, 3, result] # for example, [1, 2, 3, None] -> [1, 2, 3, result]
if isinstance(result, list): if isinstance(result, list):
raise ConfigError('invalid calculated value returned' raise ConfigError('invalid calculated value returned'
@ -259,23 +263,20 @@ class Config(object):
if opt_or_descr.is_mandatory() and mandatory: if opt_or_descr.is_mandatory() and mandatory:
if self._is_empty(opt_or_descr) and \ if self._is_empty(opt_or_descr) and \
opt_or_descr.is_empty_by_default(): opt_or_descr.is_empty_by_default():
raise MandatoryError("option: {0} is mandatory " raise MandatoryError("option: {0} is mandatory "
"and shall have a value".format(name)) "and shall have a value".format(name))
# frozen and force default # frozen and force default
if opt_or_descr.is_forced_on_freeze(): if opt_or_descr.is_forced_on_freeze():
return opt_or_descr.getdefault() return opt_or_descr.getdefault()
return self._cfgimpl_values[name] return self._cfgimpl_values[name]
# def __dir__(self):
# #from_type = dir(type(self))
# from_dict = list(self.__dict__)
# extras = list(self._cfgimpl_values)
# return sorted(set(extras + from_dict))
def unwrap_from_name(self, name): def unwrap_from_name(self, name):
# didn't have to stoop so low: `self.get()` must be the proper method """convenience method to extract and Option() object from the Config()
# **and it is slow**: it recursively searches into the namespaces **and it is slow**: it recursively searches into the namespaces
:returns: Option()
"""
paths = self.getpaths(allpaths=True) paths = self.getpaths(allpaths=True)
opts = dict([(path, self.unwrap_from_path(path)) for path in paths]) opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
all_paths = [p.split(".") for p in self.getpaths()] all_paths = [p.split(".") for p in self.getpaths()]
@ -283,17 +284,21 @@ class Config(object):
if name in pth: if name in pth:
return opts[".".join(pth)] return opts[".".join(pth)]
raise NotFoundError("name: {0} not found".format(name)) raise NotFoundError("name: {0} not found".format(name))
def unwrap_from_path(self, path): def unwrap_from_path(self, path):
# didn't have to stoop so low, `geattr(self, path)` is much better """convenience method to extract and Option() object from the Config()
# **fast**: finds the option directly in the appropriate namespace and it is **fast**: finds the option directly in the appropriate
namespace
:returns: Option()
"""
if '.' in path: if '.' in path:
homeconfig, path = self._cfgimpl_get_home_by_path(path) homeconfig, path = self._cfgimpl_get_home_by_path(path)
return getattr(homeconfig._cfgimpl_descr, path) return getattr(homeconfig._cfgimpl_descr, path)
return getattr(self._cfgimpl_descr, path) return getattr(self._cfgimpl_descr, path)
def __delattr__(self, name): def __delattr__(self, name):
# if you use delattr you are responsible for all bad things happening "if you use delattr you are responsible for all bad things happening"
if name.startswith('_cfgimpl_'): if name.startswith('_cfgimpl_'):
del self.__dict__[name] del self.__dict__[name]
return return
@ -304,12 +309,17 @@ class Config(object):
self._cfgimpl_values[name] = getattr(opt, 'default', None) self._cfgimpl_values[name] = getattr(opt, 'default', None)
def setoption(self, name, value, who=None): def setoption(self, name, value, who=None):
#who is **not necessarily** a owner, because it cannot be a list """effectively modifies the value of an Option()
#FIXME : sortir le setoption pour les multi, ca ne devrait pas être la (typically called by the __setattr__)
:param who: is an owner's name
who is **not necessarily** a owner, because it cannot be a list
:type who: string
"""
child = getattr(self._cfgimpl_descr, name) child = getattr(self._cfgimpl_descr, name)
if who == None: if who == None:
if child.is_multi(): if child.is_multi():
newowner = [self._cfgimpl_owner for i in range(len(value))] newowner = [self._cfgimpl_owner for i in range(len(value))]
else: else:
newowner = self._cfgimpl_owner newowner = self._cfgimpl_owner
else: else:
@ -323,7 +333,7 @@ class Config(object):
" {0} that is set to multi".format(name)) " {0} that is set to multi".format(name))
newowner = [who for i in range(len(value))] newowner = [who for i in range(len(value))]
else: else:
newowner = who newowner = who
if type(child) != SymLinkOption: if type(child) != SymLinkOption:
if child.has_callback() and who=='default': if child.has_callback() and who=='default':
raise TypeError("trying to set a value to an option " raise TypeError("trying to set a value to an option "
@ -336,13 +346,20 @@ class Config(object):
child.setowner(self, ['default' for i in range(len(child.getdefault()))]) child.setowner(self, ['default' for i in range(len(child.getdefault()))])
self._cfgimpl_values[name] = Multi(copy(child.getdefault()), self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
config=self, child=child) config=self, child=child)
else: else:
child.setowner(self, newowner) child.setowner(self, newowner)
else: else:
homeconfig = self._cfgimpl_get_toplevel() homeconfig = self._cfgimpl_get_toplevel()
child.setoption(homeconfig, value, who) child.setoption(homeconfig, value, who)
def set(self, **kwargs): def set(self, **kwargs):
"""
"do what I mean"-interface to option setting. Searches all paths
starting from that config for matches of the optional arguments
and sets the found option if the match is not ambiguous.
:param kwargs: dict of name strings to values.
"""
all_paths = [p.split(".") for p in self.getpaths(allpaths=True)] all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
for key, value in kwargs.iteritems(): for key, value in kwargs.iteritems():
key_p = key.split('.') key_p = key.split('.')
@ -362,79 +379,87 @@ class Config(object):
'more than one option that ends with %s' % (key, )) 'more than one option that ends with %s' % (key, ))
else: else:
raise NoMatchingOptionFound( raise NoMatchingOptionFound(
'there is no option that matches %s' 'there is no option that matches %s'
' or the option is hidden or disabled'% (key, )) ' or the option is hidden or disabled'% (key, ))
def get(self, name): def get(self, name):
"""
much like the attribute access way, except that
the search for the option is performed recursively in the whole
configuration tree.
**carefull**: very slow !
"""
paths = self.getpaths(allpaths=True) paths = self.getpaths(allpaths=True)
pathsvalues = [] pathsvalues = []
for path in paths: for path in paths:
pathname = path.split('.')[-1] pathname = path.split('.')[-1]
if pathname == name: if pathname == name:
try: try:
value = getattr(self, path) value = getattr(self, path)
return value return value
except Exception, e: except Exception, e:
raise e raise e
raise NotFoundError("option {0} not found in config".format(name)) raise NotFoundError("option {0} not found in config".format(name))
def _cfgimpl_get_home_by_path(self, path): def _cfgimpl_get_home_by_path(self, path):
"""returns tuple (config, name)""" """:returns: tuple (config, name)"""
path = path.split('.') path = path.split('.')
for step in path[:-1]: for step in path[:-1]:
self = getattr(self, step) self = getattr(self, step)
return self, path[-1] return self, path[-1]
def _cfgimpl_get_toplevel(self): def _cfgimpl_get_toplevel(self):
":returns: root config"
while self._cfgimpl_parent is not None: while self._cfgimpl_parent is not None:
self = self._cfgimpl_parent self = self._cfgimpl_parent
return self return self
def _cfgimpl_get_path(self): def _cfgimpl_get_path(self):
"the path in the attribute access meaning."
subpath = [] subpath = []
obj = self obj = self
while obj._cfgimpl_parent is not None: while obj._cfgimpl_parent is not None:
subpath.insert(0, obj._cfgimpl_descr._name) subpath.insert(0, obj._cfgimpl_descr._name)
obj = obj._cfgimpl_parent obj = obj._cfgimpl_parent
return ".".join(subpath) return ".".join(subpath)
# ______________________________________________________________________
def cfgimpl_previous_value(self, path): def cfgimpl_previous_value(self, path):
"stores the previous value"
home, name = self._cfgimpl_get_home_by_path(path) home, name = self._cfgimpl_get_home_by_path(path)
return home._cfgimpl_previous_values[name] return home._cfgimpl_previous_values[name]
def get_previous_value(self, name): def get_previous_value(self, name):
"for the time being, only the previous Option's value is accessible"
return self._cfgimpl_previous_values[name] return self._cfgimpl_previous_values[name]
# ______________________________________________________________________
def add_warning(self, warning): def add_warning(self, warning):
"Config implements its own warning pile. Could be useful"
self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning) self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
def get_warnings(self): def get_warnings(self):
"Config implements its own warning pile"
return self._cfgimpl_get_toplevel()._cfgimpl_warnings return self._cfgimpl_get_toplevel()._cfgimpl_warnings
# ____________________________________________________________ # ____________________________________________________________
# freeze and read-write statuses # Config()'s status
def cfgimpl_freeze(self): def cfgimpl_freeze(self):
"cannot modify the frozen `Option`'s"
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = True rootconfig._cfgimpl_frozen = True
self._cfgimpl_frozen = True self._cfgimpl_frozen = True
def cfgimpl_unfreeze(self): def cfgimpl_unfreeze(self):
"can modify the Options that are frozen"
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_frozen = False rootconfig._cfgimpl_frozen = False
self._cfgimpl_frozen = False self._cfgimpl_frozen = False
def is_frozen(self): def is_frozen(self):
# it should be the same value as self._cfgimpl_frozen... "freeze flag at Config level"
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_frozen return rootconfig._cfgimpl_frozen
def is_mandatory(self):
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_mandatory
def cfgimpl_read_only(self): def cfgimpl_read_only(self):
# convenience method to freeze, hidde and disable "convenience method to freeze, hidde and disable"
self.cfgimpl_freeze() self.cfgimpl_freeze()
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
rootconfig.cfgimpl_disable_property('hidden') rootconfig.cfgimpl_disable_property('hidden')
@ -442,12 +467,35 @@ class Config(object):
rootconfig._cfgimpl_mandatory = True rootconfig._cfgimpl_mandatory = True
def cfgimpl_read_write(self): def cfgimpl_read_write(self):
# convenience method to freeze, hidde and disable "convenience method to freeze, hidde and disable"
self.cfgimpl_freeze() self.cfgimpl_freeze()
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
rootconfig.cfgimpl_enable_property('hidden') rootconfig.cfgimpl_enable_property('hidden')
rootconfig.cfgimpl_enable_property('disabled') rootconfig.cfgimpl_enable_property('disabled')
rootconfig._cfgimpl_mandatory = False rootconfig._cfgimpl_mandatory = False
def cfgimpl_non_mandatory(self):
"""mandatory at the Config level means that the Config raises an error
if a mandatory option is found"""
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory shall"
" not be used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = False
def cfgimpl_mandatory(self):
"""mandatory at the Config level means that the Config raises an error
if a mandatory option is found"""
if self._cfgimpl_parent != None:
raise MethodCallError("this method root_mandatory shall"
" not be used with non-root Confit() object")
rootconfig = self._cfgimpl_get_toplevel()
rootconfig._cfgimpl_mandatory = True
def is_mandatory(self):
"all mandatory Options shall have a value"
rootconfig = self._cfgimpl_get_toplevel()
return rootconfig._cfgimpl_mandatory
# ____________________________________________________________ # ____________________________________________________________
def getkey(self): def getkey(self):
return self._cfgimpl_descr.getkey(self) return self._cfgimpl_descr.getkey(self)
@ -456,13 +504,15 @@ class Config(object):
return hash(self.getkey()) return hash(self.getkey())
def __eq__(self, other): def __eq__(self, other):
"Config comparison"
return self.getkey() == other.getkey() return self.getkey() == other.getkey()
def __ne__(self, other): def __ne__(self, other):
"Config comparison"
return not self == other return not self == other
# ______________________________________________________________________
def __iter__(self): def __iter__(self):
# iteration only on Options (not OptionDescriptions) "iteration only on Options (not OptionDescriptions)"
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, Option): if isinstance(child, Option):
try: try:
@ -481,12 +531,13 @@ class Config(object):
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
try: try:
if child.get_group_type() in groups: if child.get_group_type() in groups:
yield child._name, getattr(self, child._name) yield child._name, getattr(self, child._name)
except: except:
pass # hidden, disabled option pass # hidden, disabled option
# ______________________________________________________________________
def __str__(self, indent=""): def __str__(self, indent=""):
"Config's string representation"
lines = [] lines = []
children = [(child._name, child) children = [(child._name, child)
for child in self._cfgimpl_descr._children] for child in self._cfgimpl_descr._children]
@ -507,25 +558,27 @@ class Config(object):
return '\n'.join(lines) return '\n'.join(lines)
def getpaths(self, include_groups=False, allpaths=False, mandatory=False): def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
"""returns a list of all paths in self, recursively, taking care of """returns a list of all paths in self, recursively, taking care of
the context of properties (hidden/disabled) the context of properties (hidden/disabled)
""" """
paths = [] paths = []
for path in self._cfgimpl_descr.getpaths(include_groups=include_groups): for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
try: try:
value = getattr(self, path) value = getattr(self, path)
except MandatoryError: except MandatoryError:
if mandatory or allpaths: if mandatory or allpaths:
paths.append(path) paths.append(path)
except PropertiesOptionError: except PropertiesOptionError:
if allpaths: if allpaths:
paths.append(path) # hidden or disabled or mandatory option added paths.append(path) # option which have properties added
else: else:
paths.append(path) paths.append(path)
return paths return paths
def make_dict(config, flatten=False): def make_dict(config, flatten=False):
"""export the whole config into a `dict`
:returns: dict of Option's name (or path) and values"""
paths = config.getpaths() paths = config.getpaths()
pathsvalues = [] pathsvalues = []
for path in paths: for path in paths:
@ -534,14 +587,19 @@ def make_dict(config, flatten=False):
else: else:
pathname = path pathname = path
try: try:
value = getattr(config, path) value = getattr(config, path)
pathsvalues.append((pathname, value)) pathsvalues.append((pathname, value))
except: except:
pass # this just a hidden or disabled option pass # this just a hidden or disabled option
options = dict(pathsvalues) options = dict(pathsvalues)
return options return options
def mandatory_warnings(config): def mandatory_warnings(config):
"""convenience function to trace Options that are mandatory and
where no value has been set
:returns: generator of mandatory Option's path
"""
mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
for path in config._cfgimpl_descr.getpaths(include_groups=True): for path in config._cfgimpl_descr.getpaths(include_groups=True):

View file

@ -16,12 +16,12 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# The original `Config` design model is unproudly borrowed from # The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.basetype import HiddenBaseType, DisabledBaseType from tiramisu.basetype import HiddenBaseType, DisabledBaseType
from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError, from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
RequiresError, RequirementRecursionError, MandatoryError) RequiresError, RequirementRecursionError, MandatoryError)
requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')] requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')]
@ -31,25 +31,30 @@ for act1, act2 in requires_actions:
available_actions.extend([act1, act2]) available_actions.extend([act1, act2])
reverse_actions[act1] = act2 reverse_actions[act1] = act2
reverse_actions[act2] = act1 reverse_actions[act2] = act1
# ____________________________________________________________ # ____________________________________________________________
# OptionDescription authorized group_type values # OptionDescription authorized group_type values
"""
Three available group_types : `default`, `family`, `group` and
`master` (for master~slave group type). Notice that for a
master~slave group, the name of the group and the name of the
master option are identical.
"""
group_types = ['default', 'family', 'group', 'master'] group_types = ['default', 'family', 'group', 'master']
# ____________________________________________________________ # ____________________________________________________________
# multi types # multi types
class Multi(list): class Multi(list):
"container that support items for the values of list (multi) options" "container that support items for the values of list (multi) options"
def __init__(self, lst, config, child): def __init__(self, lst, config, child):
self.config = config self.config = config
self.child = child self.child = child
super(Multi, self).__init__(lst) super(Multi, self).__init__(lst)
def __setitem__(self, key, value): def __setitem__(self, key, value):
return self.setoption(value, key) return self.setoption(value, key)
def append(self, value): def append(self, value):
self.setoption(value) self.setoption(value)
def setoption(self, value, key=None): def setoption(self, value, key=None):
owners = self.child.getowner(self.config) owners = self.child.getowner(self.config)
# None is replaced by default_multi # None is replaced by default_multi
@ -77,7 +82,7 @@ class Multi(list):
self.config._cfgimpl_previous_values[self.child._name] = oldvalue self.config._cfgimpl_previous_values[self.child._name] = oldvalue
self.child.setowner(self.config, oldowner) self.child.setowner(self.config, oldowner)
return ret return ret
def pop(self, key): def pop(self, key):
oldowner = self.child.getowner(self.config) oldowner = self.child.getowner(self.config)
oldowner.pop(key) oldowner.pop(key)
@ -86,11 +91,16 @@ class Multi(list):
# ____________________________________________________________ # ____________________________________________________________
# #
class Option(HiddenBaseType, DisabledBaseType): class Option(HiddenBaseType, DisabledBaseType):
#reminder: an Option object is **not** a container for the value """
Abstract base class for configuration option's
reminder: an Option object is **not** a container for the value
"""
"freeze means: cannot modify the value of an Option once set"
_frozen = False _frozen = False
"if an Option has been frozen, shall return the default value"
_force_default_on_freeze = False _force_default_on_freeze = False
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, mandatory=False, multi=False, callback=None, requires=None, mandatory=False, multi=False, callback=None,
callback_params=None): callback_params=None):
self._name = name self._name = name
self.doc = doc self.doc = doc
@ -99,20 +109,20 @@ class Option(HiddenBaseType, DisabledBaseType):
self.multi = multi self.multi = multi
if not self.multi and default_multi is not None: if not self.multi and default_multi is not None:
raise ConfigError("a default_multi is set whereas multi is False" raise ConfigError("a default_multi is set whereas multi is False"
" in option: {0}".format(name)) " in option: {0}".format(name))
if default_multi is not None and not self._validate(default_multi): if default_multi is not None and not self._validate(default_multi):
raise ConfigError("invalid default_multi value {0} " raise ConfigError("invalid default_multi value {0} "
"for option {1}".format(str(default_multi), name)) "for option {1}".format(str(default_multi), name))
self.default_multi = default_multi self.default_multi = default_multi
#if self.multi and default_multi is None: #if self.multi and default_multi is None:
# _cfgimpl_warnings[name] = DefaultMultiWarning # _cfgimpl_warnings[name] = DefaultMultiWarning
if callback is not None and (default is not None or default_multi is not None): if callback is not None and (default is not None or default_multi is not None):
raise ConfigError("defaut values not allowed if option: {0} " raise ConfigError("defaut values not allowed if option: {0} "
"is calculated".format(name)) "is calculated".format(name))
self.callback = callback self.callback = callback
if self.callback is None and callback_params is not None: if self.callback is None and callback_params is not None:
raise ConfigError("params defined for a callback function but" raise ConfigError("params defined for a callback function but"
" no callback defined yet for option {0}".format(name)) " no callback defined yet for option {0}".format(name))
self.callback_params = callback_params self.callback_params = callback_params
if self.multi == True: if self.multi == True:
if default == None: if default == None:
@ -122,19 +132,19 @@ class Option(HiddenBaseType, DisabledBaseType):
"for option {1} : not list type".format(str(default), name)) "for option {1} : not list type".format(str(default), name))
else: else:
if default != None and not self.validate(default): if default != None and not self.validate(default):
raise ConfigError("invalid default value {0} " raise ConfigError("invalid default value {0} "
"for option {1}".format(str(default), name)) "for option {1}".format(str(default), name))
self.default = default self.default = default
self.properties = [] # 'hidden', 'disabled'... self.properties = [] # 'hidden', 'disabled'...
def validate(self, value): def validate(self, value):
if self.multi == False: if self.multi == False:
# None allows the reset of the value # None allows the reset of the value
if value != None: if value != None:
return self._validate(value) return self._validate(value)
else: else:
if not isinstance(value, list): if not isinstance(value, list):
raise ConfigError("invalid value {0} " raise ConfigError("invalid value {0} "
"for option {1} which must be a list".format(value, "for option {1} which must be a list".format(value,
self._name)) self._name))
for val in value: for val in value:
@ -145,41 +155,53 @@ class Option(HiddenBaseType, DisabledBaseType):
return True return True
def getdefault(self): def getdefault(self):
"accessing the default value"
return self.default return self.default
def is_empty_by_default(self): def is_empty_by_default(self):
"no default value has been set yet"
if ((not self.is_multi() and self.default == None) or if ((not self.is_multi() and self.default == None) or
(self.is_multi() and self.default == []) or None in self.default): (self.is_multi() and self.default == []) or None in self.default):
return True return True
return False return False
def force_default(self): def force_default(self):
"if an Option has been frozen, shall return the default value"
self._force_default_on_freeze = True self._force_default_on_freeze = True
def hascallback_and_isfrozen(): def hascallback_and_isfrozen():
return self._frozen and self.has_callback() return self._frozen and self.has_callback()
def is_forced_on_freeze(self): def is_forced_on_freeze(self):
"if an Option has been frozen, shall return the default value"
return self._frozen and self._force_default_on_freeze return self._frozen and self._force_default_on_freeze
def getdoc(self): def getdoc(self):
"accesses the Option's doc"
return self.doc return self.doc
def getcallback(self): def getcallback(self):
"a callback is only a link, the name of an external hook"
return self.callback return self.callback
def has_callback(self): def has_callback(self):
"to know if a callback has been defined or not"
if self.callback == None: if self.callback == None:
return False return False
else: else:
return True return True
def getcallback_params(self): def getcallback_params(self):
"if a callback has been defined, returns his arity"
return self.callback_params return self.callback_params
def setowner(self, config, owner): def setowner(self, config, owner):
# config *must* be only the **parent** config (not the toplevel config) """
# owner is a **real* owner, a list is actually allowable here :param config: *must* be only the **parent** config
(not the toplevel config)
:param owner: is a **real* owner, that is a name or a list
which is allowable here
"""
name = self._name name = self._name
if self.is_multi(): if self.is_multi():
if not type(owner) == list: if not type(owner) == list:
@ -188,11 +210,14 @@ class Option(HiddenBaseType, DisabledBaseType):
config._cfgimpl_value_owners[name] = owner config._cfgimpl_value_owners[name] = owner
def getowner(self, config): def getowner(self, config):
# config *must* be only the **parent** config (not the toplevel config) "config *must* be only the **parent** config (not the toplevel config)"
return config._cfgimpl_value_owners[self._name] return config._cfgimpl_value_owners[self._name]
def setoption(self, config, value, who): def setoption(self, config, value, who):
"who is **not necessarily** a owner because it cannot be a list" """changes the option's value with the value_owner's who
:param config: the parent config is necessary here to store the value
:param who : is **not necessarily** a owner because it cannot be a list
:type who: string """
name = self._name name = self._name
if not self.validate(value): if not self.validate(value):
raise ConfigError('invalid value %s for option %s' % (value, name)) raise ConfigError('invalid value %s for option %s' % (value, name))
@ -231,39 +256,37 @@ class Option(HiddenBaseType, DisabledBaseType):
# #option = getattr(config._cfgimpl_descr, reqname) # #option = getattr(config._cfgimpl_descr, reqname)
# # if not option.multi == True: # # if not option.multi == True:
# # raise ConflictConfigError("an option with requires " # # raise ConflictConfigError("an option with requires "
# # "has to be a list type : {0}".format(name)) # # "has to be a list type : {0}".format(name))
# if len(config._cfgimpl_values[reqname]) != len(value): # if len(config._cfgimpl_values[reqname]) != len(value):
# raise ConflictConfigError("an option with requires " # raise ConflictConfigError("an option with requires "
# "has not the same length of the others " # "has not the same length of the others "
# "in the group : {0}".format(reqname)) # "in the group : {0}".format(reqname))
if type(config._cfgimpl_values[name]) == Multi: if type(config._cfgimpl_values[name]) == Multi:
config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name]) config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name])
else: else:
config._cfgimpl_previous_values[name] = config._cfgimpl_values[name] config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
config._cfgimpl_values[name] = value config._cfgimpl_values[name] = value
def getkey(self, value): def getkey(self, value):
return value return value
# ____________________________________________________________ # ____________________________________________________________
"freeze utility"
def freeze(self): def freeze(self):
self._frozen = True self._frozen = True
return True return True
def unfreeze(self): def unfreeze(self):
self._frozen = False self._frozen = False
def is_frozen(self): def is_frozen(self):
return self._frozen return self._frozen
# ____________________________________________________________ # ____________________________________________________________
def is_multi(self): def is_multi(self):
return self.multi return self.multi
def is_mandatory(self): def is_mandatory(self):
return self._mandatory return self._mandatory
class ChoiceOption(Option): class ChoiceOption(Option):
opt_type = 'string' opt_type = 'string'
def __init__(self, name, doc, values, default=None, def __init__(self, name, doc, values, default=None,
requires=None, callback=None, callback_params=None, requires=None, callback=None, callback_params=None,
multi=False, mandatory=False, open_values=False): multi=False, mandatory=False, open_values=False):
@ -273,7 +296,7 @@ class ChoiceOption(Option):
'{0}'.format(name)) '{0}'.format(name))
self.open_values = open_values self.open_values = open_values
super(ChoiceOption, self).__init__(name, doc, default=default, super(ChoiceOption, self).__init__(name, doc, default=default,
callback=callback, callback_params=callback_params, callback=callback, callback_params=callback_params,
requires=requires, multi=multi, mandatory=mandatory) requires=requires, multi=multi, mandatory=mandatory)
def _validate(self, value): def _validate(self, value):
@ -284,11 +307,11 @@ class ChoiceOption(Option):
class BoolOption(Option): class BoolOption(Option):
opt_type = 'bool' opt_type = 'bool'
def _validate(self, value): def _validate(self, value):
return isinstance(value, bool) return isinstance(value, bool)
# config level validator # config level validator
# def setoption(self, config, value, who): # def setoption(self, config, value, who):
# name = self._name # name = self._name
# if value and self._validator is not None: # if value and self._validator is not None:
@ -298,7 +321,7 @@ class BoolOption(Option):
class IntOption(Option): class IntOption(Option):
opt_type = 'int' opt_type = 'int'
def _validate(self, value): def _validate(self, value):
return isinstance(value, int) return isinstance(value, int)
@ -310,36 +333,36 @@ class FloatOption(Option):
class StrOption(Option): class StrOption(Option):
opt_type = 'string' opt_type = 'string'
def _validate(self, value): def _validate(self, value):
return isinstance(value, str) return isinstance(value, str)
class SymLinkOption(object): class SymLinkOption(object):
opt_type = 'symlink' opt_type = 'symlink'
def __init__(self, name, path): def __init__(self, name, path):
self._name = name self._name = name
self.path = path self.path = path
def setoption(self, config, value, who): def setoption(self, config, value, who):
setattr(config, self.path, value) # .setoption(self.path, value, who) setattr(config, self.path, value) # .setoption(self.path, value, who)
class IPOption(Option): class IPOption(Option):
opt_type = 'ip' opt_type = 'ip'
def _validate(self, value): def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead # by now the validation is nothing but a string, use IPy instead
return isinstance(value, str) return isinstance(value, str)
class NetmaskOption(Option): class NetmaskOption(Option):
opt_type = 'netmask' opt_type = 'netmask'
def _validate(self, value): def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead # by now the validation is nothing but a string, use IPy instead
return isinstance(value, str) return isinstance(value, str)
class ArbitraryOption(Option): class ArbitraryOption(Option):
def __init__(self, name, doc, default=None, defaultfactory=None, def __init__(self, name, doc, default=None, defaultfactory=None,
requires=None, multi=False, mandatory=False): requires=None, multi=False, mandatory=False):
super(ArbitraryOption, self).__init__(name, doc, requires=requires, super(ArbitraryOption, self).__init__(name, doc, requires=requires,
multi=multi, mandatory=mandatory) multi=multi, mandatory=mandatory)
@ -356,16 +379,21 @@ class ArbitraryOption(Option):
return self.default return self.default
class OptionDescription(HiddenBaseType, DisabledBaseType): class OptionDescription(HiddenBaseType, DisabledBaseType):
"Config's schema (organisation) and container of Options"
"the group_type is an attribute useful for iteration on groups in a config"
group_type = 'default' group_type = 'default'
def __init__(self, name, doc, children, requires=None): def __init__(self, name, doc, children, requires=None):
"""
:param children: is a list of option descriptions (including
``OptionDescription`` instances for nested namespaces).
"""
self._name = name self._name = name
self.doc = doc self.doc = doc
self._children = children self._children = children
self._requires = requires self._requires = requires
self._build() self._build()
self.properties = [] # 'hidden', 'disabled'... self.properties = [] # 'hidden', 'disabled'...
def getdoc(self): def getdoc(self):
return self.doc return self.doc
@ -383,12 +411,12 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
child._name)) child._name))
self._children.append(child) self._children.append(child)
setattr(self, child._name, child) setattr(self, child._name, child)
def update_child(self, child): def update_child(self, child):
"modification of an existing option" "modification of an existing option"
# XXX : corresponds to the `redefine`, is it usefull # XXX : corresponds to the `redefine`, is it usefull
pass pass
def getkey(self, config): def getkey(self, config):
return tuple([child.getkey(getattr(config, child._name)) return tuple([child.getkey(getattr(config, child._name))
for child in self._children]) for child in self._children])
@ -415,40 +443,35 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
paths.append('.'.join(currpath + [attr])) paths.append('.'.join(currpath + [attr]))
return paths return paths
# ____________________________________________________________ # ____________________________________________________________
def set_group_type(self, group_type): def set_group_type(self, group_type):
":param group_type: string in group_types"
if group_type in group_types: if group_type in group_types:
self.group_type = group_type self.group_type = group_type
else: else:
raise ConfigError('not allowed value for group_type : {0}'.format( raise ConfigError('not allowed value for group_type : {0}'.format(
group_type)) group_type))
def get_group_type(self): def get_group_type(self):
return self.group_type return self.group_type
# ____________________________________________________________ # ____________________________________________________________
"actions API"
def hide(self): def hide(self):
super(OptionDescription, self).hide() super(OptionDescription, self).hide()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children: for child in self._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
child.hide() child.hide()
def show(self): def show(self):
# FIXME : AND THE SUBCHILDREN ??
super(OptionDescription, self).show() super(OptionDescription, self).show()
for child in self._children: for child in self._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
child.show() child.show()
# ____________________________________________________________
def disable(self): def disable(self):
super(OptionDescription, self).disable() super(OptionDescription, self).disable()
# FIXME : AND THE SUBCHILDREN ?
for child in self._children: for child in self._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
child.disable() child.disable()
def enable(self): def enable(self):
# FIXME : AND THE SUBCHILDREN ?
super(OptionDescription, self).enable() super(OptionDescription, self).enable()
for child in self._children: for child in self._children:
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
@ -456,7 +479,7 @@ class OptionDescription(HiddenBaseType, DisabledBaseType):
# ____________________________________________________________ # ____________________________________________________________
def validate_requires_arg(requires, name): def validate_requires_arg(requires, name):
# malformed requirements "malformed requirements"
config_action = [] config_action = []
for req in requires: for req in requires:
if not type(req) == tuple and len(req) != 3: if not type(req) == tuple and len(req) != 3:
@ -474,6 +497,7 @@ def validate_requires_arg(requires, name):
config_action.append(action) config_action.append(action)
def build_actions(requires): def build_actions(requires):
"action are hide, show, enable, disable..."
trigger_actions = {} trigger_actions = {}
for require in requires: for require in requires:
action = require[2] action = require[2]
@ -481,6 +505,7 @@ def build_actions(requires):
return trigger_actions return trigger_actions
def apply_requires(opt, config): def apply_requires(opt, config):
"carries out the jit (just in time requirements between options"
if hasattr(opt, '_requires') and opt._requires is not None: if hasattr(opt, '_requires') and opt._requires is not None:
rootconfig = config._cfgimpl_get_toplevel() rootconfig = config._cfgimpl_get_toplevel()
validate_requires_arg(opt._requires, opt._name) validate_requires_arg(opt._requires, opt._name)
@ -508,4 +533,3 @@ def apply_requires(opt, config):
# no callback has been triggered, then just reverse the action # no callback has been triggered, then just reverse the action
if not matches: if not matches:
getattr(opt, reverse_actions[action])() getattr(opt, reverse_actions[action])()