Merge branch 'master' into lgpl
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 22 Sep 2013 20:01:19 +0000 (22:01 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 22 Sep 2013 20:01:19 +0000 (22:01 +0200)
Conflicts:
setup.py

49 files changed:
AUTHORS
Makefile
VERSION [new file with mode: 0644]
doc/api.txt
doc/config.png [new file with mode: 0644]
doc/config.svg [new file with mode: 0644]
doc/config.txt
doc/consistency.txt
doc/doctest.txt
doc/getting-started.txt
doc/index.txt
doc/logo.png [new file with mode: 0644]
doc/option.txt
doc/storage.png [new file with mode: 0644]
doc/storage.svg [new file with mode: 0644]
doc/storage.txt [new file with mode: 0644]
doc/tiramisu.jpeg [deleted file]
setup.py
test/test_cache.py
test/test_config.py
test/test_config_api.py
test/test_config_ip.py
test/test_option_calculation.py
test/test_option_consistency.py
test/test_option_setting.py
test/test_option_validator.py [new file with mode: 0644]
test/test_parsing_group.py
test/test_requires.py
test/test_slots.py
test/test_state.py [new file with mode: 0644]
test/test_storage.py
tiramisu/autolib.py
tiramisu/config.py
tiramisu/option.py
tiramisu/setting.py
tiramisu/storage/__init__.py
tiramisu/storage/dictionary/__init__.py
tiramisu/storage/dictionary/setting.py
tiramisu/storage/dictionary/storage.py
tiramisu/storage/dictionary/value.py
tiramisu/storage/sqlite3/__init__.py
tiramisu/storage/sqlite3/setting.py
tiramisu/storage/sqlite3/sqlite3db.py [new file with mode: 0644]
tiramisu/storage/sqlite3/storage.py
tiramisu/storage/sqlite3/value.py
tiramisu/storage/util.py [new file with mode: 0644]
tiramisu/value.py
translations/fr/tiramisu.po
translations/tiramisu.pot

diff --git a/AUTHORS b/AUTHORS
index f6e3bb8..1819d02 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,3 +6,5 @@ Emmanuel Garette <egarette@cadoles.com> developer
 
 Daniel Dehennin <daniel.dehennin@ac-dijon.fr> contributor
 Philippe Caseiro <pcaseiro@cadoles.com> contributor
+
+Gerald Schwartzmann <gschartzmann@cadoles.com> tiramisu's logo (made with The Gimp)
index fce5f8d..e9fabdb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,19 +15,28 @@ ifneq ($(DESTDIR),)
 PYTHON_OPTS     += --root $(DESTDIR)
 endif
 
-LAST_TAG        := $(shell git describe --tags --abbrev=0)
-VERSION         := $(shell echo $(LAST_TAG) | awk -F'/' '{print $$2}' || true)
-VERSION_FILE    := version.in
+VERSION                        :=      `cat VERSION`
+
+define gettext
+    if command -v pygettext >/dev/null 2>&1 ; then \
+        P="pygettext" ; \
+    else \
+           P="pygettext.py" ; \
+    fi ; \
+       $$P -p translations/ -o $(PACKAGE).pot `find $(PACKAGE)/ -name "*.py"`
+endef
 
 # Build translation files
 define build_translation
     if [ -d ${1} ]; then                                            \
        for f in `find ${1} -name "*.po"`; do                       \
-           msgfmt -o `dirname $$f`/`basename -s ".po" $$f`.mo $$f || true; \
+           msgfmt -o `dirname $$f`/`basename $$f ".po"`.mo $$f || true; \
        done;                                                       \
     fi
 endef
 
+
+
 # Install Traduction
 define install_translation
     if [ -d ${1} ]; then                          \
@@ -39,38 +48,33 @@ define install_translation
     fi
 endef
 
-all:
+all: build-lang
 
 clean:
        $(RM) -r build
-       $(RM) -r tiramisu.egg-info/
+       $(RM) -r $(PACKAGE).egg-info/
        $(RM) -r $(TRADUC_DIR)/*/*.mo
 
 #test: clean
 #      py.test
 
+# Build or update Portable Object Base Translation for gettext
+
+build-pot:
+       $(call gettext)
+
 build-lang:
        $(call build_translation, $(TRADUC_DIR))
 
-install-lang: build-lang
+install-lang:
        $(INSTALL_DIR) $(TRADUC_DEST)
        $(call install_translation, $(TRADUC_DIR))
 
-install: version.in install-lang
+install: install-lang
        python setup.py install --no-compile $(PYTHON_OPTS)
 
+dist:
+       git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ HEAD | gzip -9 > $(PACKAGE)-$(VERSION).tar.gz
+
 # List in .PHONY to force generation at each call
-version.in:
-       @if test -n $(VERSION) ; then \
-               echo -n $(VERSION) > $(VERSION_FILE) ; \
-       fi
-       @if test ! -s $(VERSION_FILE); then \
-               echo -n '0.0-dev' > $(VERSION_FILE); \
-       fi
-
-dist: version.in
-       git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ -o $(PACKAGE)-$(VERSION).tar $(LAST_TAG) \
-         && tar --xform "s,\(.*\),$(PACKAGE)-$(VERSION)/\1," -f $(PACKAGE)-$(VERSION).tar -r version.in \
-         && gzip -9 $(PACKAGE)-$(VERSION).tar
-
-.PHONY: all clean test install version.in dist
+.PHONY: all clean build-pot build-lang install-lang install dist
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..0f82de4
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0rc1
index 1498f14..7fb526f 100644 (file)
@@ -11,4 +11,5 @@ Auto generated library's API
     tiramisu.value
     tiramisu.autolib
     tiramisu.error
+    tiramisu.storage
 
diff --git a/doc/config.png b/doc/config.png
new file mode 100644 (file)
index 0000000..a468275
Binary files /dev/null and b/doc/config.png differ
diff --git a/doc/config.svg b/doc/config.svg
new file mode 100644 (file)
index 0000000..3ff4bc9
--- /dev/null
@@ -0,0 +1,257 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="400"
+   height="200"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="test.svg"
+   inkscape:export-filename="/home/gnunux/git/tiramisu/doc/storage.png"
+   inkscape:export-xdpi="135"
+   inkscape:export-ydpi="135">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective3827" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="73.881208"
+     inkscape:cy="154.11692"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="841"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-852.36218)">
+    <g
+       id="g4206"
+       transform="translate(32.34835,646.56497)">
+      <text
+         sodipodi:linespacing="686.00001%"
+         id="text2985"
+         y="368.36218"
+         x="98"
+         style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="368.36218"
+           x="98"
+           id="tspan2987"
+           sodipodi:role="line">Config</tspan></text>
+      <rect
+         y="351.36218"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+    </g>
+    <g
+       id="g4211"
+       transform="translate(-21.922096,643.64303)">
+      <rect
+         y="312.36218"
+         x="189.5"
+         height="30"
+         width="63"
+         id="rect3757-2"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text3777"
+         y="325.76599"
+         x="220.51762"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="325.76599"
+           x="220.51762"
+           id="tspan3779"
+           sodipodi:role="line">Option</tspan><tspan
+           y="335.76599"
+           x="220.51762"
+           sodipodi:role="line"
+           id="tspan3022">Description</tspan></text>
+    </g>
+    <g
+       id="g4201"
+       transform="translate(11,622)">
+      <rect
+         y="293.42468"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757-5"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text4190"
+         y="309.42468"
+         x="110.27588"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan4194"
+           y="309.42468"
+           x="110.27588"
+           sodipodi:role="line">Option</tspan></text>
+    </g>
+    <g
+       id="g4201-9"
+       transform="translate(85.749784,621.95117)">
+      <rect
+         y="293.42468"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757-5-1"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text4190-0"
+         y="309.42468"
+         x="110.27588"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan4194-2"
+           y="309.42468"
+           x="110.27588"
+           sodipodi:role="line">Option</tspan></text>
+    </g>
+    <g
+       id="g4211-4"
+       transform="translate(52.525433,602.85429)">
+      <rect
+         y="312.36218"
+         x="189.5"
+         height="30"
+         width="63"
+         id="rect3757-2-3"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text3777-0"
+         y="325.76599"
+         x="220.51762"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="325.76599"
+           x="220.51762"
+           id="tspan3779-1"
+           sodipodi:role="line">Option</tspan><tspan
+           y="335.76599"
+           x="220.51762"
+           sodipodi:role="line"
+           id="tspan3022-7">Description</tspan></text>
+    </g>
+    <g
+       id="g4201-1"
+       transform="translate(123.6527,582.89051)">
+      <rect
+         y="293.42468"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757-5-7"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text4190-2"
+         y="309.42468"
+         x="110.27588"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan4194-8"
+           y="309.42468"
+           x="110.27588"
+           sodipodi:role="line">Option</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 151.43627,945.42468 19.70537,10.58053"
+       id="path3110"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4201"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g4211"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 198.77217,956.00521 -0.21665,-10.62936"
+       id="path3112"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4211"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g4201-9"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 226.45587,956.00521 19.69159,-10.78874"
+       id="path3114"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4211"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g4211-4"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 259.11483,915.21647 -8.55152,-8.90128"
+       id="path3116"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4211-4"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g4201-1"
+       inkscape:connection-end-point="d4" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 164.25211,997.92715 15.42203,-11.92194"
+       id="path3118"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4206"
+       inkscape:connection-start-point="d4"
+       inkscape:connection-end="#g4211"
+       inkscape:connection-end-point="d4" />
+  </g>
+</svg>
index c833172..3c6d9bc 100644 (file)
@@ -6,15 +6,17 @@ Options handling basics
 
 Tiramisu is made of almost three main objects :
 
-- :class:`tiramisu.config.Config` which is the whole configuration entry point
 - :class:`tiramisu.option.Option` stands for the option types
 - :class:`tiramisu.option.OptionDescription` is the shema, the option's structure
+- :class:`tiramisu.config.Config` which is the whole configuration entry point
+
+.. image:: config.png
 
 Accessing the `Option`'s
 -------------------------
 
 The :class:`~tiramisu.config.Config` object attribute access notation stands for 
-the value of the configuration's :class:`~tiramisu.option.Option`. That is, the 
+the value of the configuration's :class:`~tiramisu.option.Option`.
 :class:`~tiramisu.config.Config`'s object attribute is the name of the option, 
 and the value is the value accessed by the `__getattr__` attribute access 
 mechanism.
@@ -49,7 +51,7 @@ are organized into a tree into nested
 as does every option group. The parts of the full name of the option are 
 separated by dots: e.g. ``cfg.optgroup.optname``.
 
-Let's make the protocol of accessing a config's attribute explicit
+Let's make the protocol of accessing a `Config`'s attribute explicit
 (because explicit is better than implicit):
 
 1. If the option has not been declared, an `AttributeError` is raised,
@@ -67,22 +69,11 @@ But there are special exceptions. We will see later on that an option can be a
 :term:`mandatory option`. A mandatory option is an option that must have a value 
 defined.
 
-Appart from this case, if no value have been set yet, the value is `None`. When 
-the option is called to retrieve a value, an exception is raised.
-
-What if a value has been set and `None` is to be returned again ? Don't worry, 
-an option value can be reseted::
-::
-
-    >>> cfg.cfgimpl_get_values().reset(gcdummy)
-    >>> cfg.dummy
-    False
-
-Setting the values of the options
-----------------------------------------
+Setting the value of an option
+------------------------------
 
-An important part of the setting of the configuration consists of setting the
-values of the configuration options. There are different ways of setting values,
+An important part of the setting's configuration consists of setting the
+value's option. There are different ways of setting values,
 the first one is of course the `__setattr__` method
 
 ::
@@ -110,14 +101,14 @@ adhere to the option description).
 Common manipulations
 ------------------------
 
-Let's perform some common manipulation on some options:
+Let's perform some common manipulation on some options
 
 >>> from tiramisu.config import Config
 >>> from tiramisu.option import UnicodeOption, OptionDescription
->>>
+>>> #
 >>> var1 = UnicodeOption('var1', 'first variable')
 >>> var2 = UnicodeOption('var2', '', u'value')
->>>
+>>> #
 >>> od1 = OptionDescription('od1', 'first OD', [var1, var2])
 >>> rootod = OptionDescription('rootod', '', [od1])
 
@@ -138,11 +129,10 @@ None
 >>> print c.od1.var2
 value
 
-let's modify a value (careful to the value's type...)
+let's modify a value (be careful to the value's type...)
 
 >>> c.od1.var1 = 'value'
 Traceback (most recent call last):
-[...]
 ValueError: invalid value value for option var1
 >>> c.od1.var1 = u'value'
 >>> print c.od1.var1
@@ -158,19 +148,17 @@ let's come back to the default value
 value
 
 The value is saved in a :class:`~tiramisu.value.Value` object. It is on this 
-object that we have to trigger the `reset`, wich take the option itself 
+object that we have to trigger the `reset`, which take the option itself 
 (`var2`) as a parameter.
 
-On the other side, in the `read_only` mode, it is not possible to modify the value::
+On the other side, in the `read_only` mode, it is not possible to modify the value
+
+>>> c.read_only()
+>>> c.od1.var2 = u'value2'
+Traceback (most recent call last):
+tiramisu.error.PropertiesOptionError: cannot change the value for option var2 this option is frozen
+
 
-    >>> c.read_only()
-    >>> c.od1.var2 = u'value2'
-    Traceback (most recent call last):
-    [...]
-    tiramisu.error.PropertiesOptionError: 
-    cannot change the value to var2 
-    for option ['frozen'] this option is frozen
-    
 let's retrieve the option `var1` description
 
 >>> var1.impl_get_information('doc')
@@ -200,7 +188,7 @@ That's why a tree of options can easily be searched. First, let's build such a t
 >>> c = Config(rootod)
 >>> c.read_write()
 
-Second, let's find an option by his name::
+Second, let's find an option by it's name::
 
     >>> print c.find(byname='var1')
     [<tiramisu.option.UnicodeOption object at 0x7ff1bf7d6ef0>, 
@@ -248,7 +236,7 @@ If the organisation in a tree is not important,
 {'var5': None, 'var4': None, 'var6': None, 'var1': u'value', 'var3': None, 
 'var2': None}
 
-.. note:: carefull with this `flatten` parameter, here we have just lost 
+.. note:: be carefull with this `flatten` parameter, here we have just lost 
                two options named `var1`
 
 One can export only interesting parts of a tree of options into a dict, for 
@@ -265,7 +253,7 @@ and of course, :meth:`~config.SubConfig.make_dict()` can be called in a subtree:
 >>> print c.od1.make_dict(withoption='var1')
 {'var1': None, 'var3': None, 'var2': None}
 
-the owners
+The owners
 ~~~~~~~~~~~
 
 .. glossary::
@@ -283,24 +271,36 @@ the owners
             
 Then let's retrieve the owner associated to an option::
 
-    >>> print c.getowner('var1')
-    default
-    >>> c.od1.var1 = u'non'
-    >>> print c.getowner('var1')
-    user
-    >>> del(c.var1)
-    >>> print c.getowner('var1')
-    default
-    
-the properties
-~~~~~~~~~~~~~~~~
+   >>> print c.getowner(var1)
+   default
+   >>> c.od1.var1 = u'no'
+   >>> print c.getowner(var1)
+   user
+   >>> del(c.var1)
+   >>> print c.getowner(var1)
+   default
+
+You can create your own owner, for example to distinguish modification made by
+one user to an other one's.
+
+   >>> from tiramisu.setting import owners
+   >>> owners.addowner('toto')
+   >>> c.cfgimpl_get_settings().setowner(owners.toto)
+   >>> print c.getowner(var1)
+   default
+   >>> c.od1.var1 = u'no'
+   >>> print c.getowner(var1)
+   toto
+The properties
+~~~~~~~~~~~~~~
 
 A property is an information on an option's state. 
 Let's create options with properties::
 
     >>> var1 = UnicodeOption('var1', '', u'value', properties=('hidden',))
     >>> var2 = UnicodeOption('var2', '', properties=('mandatory',))
-    >>> var3 = UnicodeOption('var3', '', u'value', properties=('frozen', 'inconnu'))
+    >>> var3 = UnicodeOption('var3', '', u'value', properties=('frozen', 'unknown'))
     >>> var4 = UnicodeOption('var4', '', u'value')
     >>> od1 = OptionDescription('od1', '', [var1, var2, var3])
     >>> od2 = OptionDescription('od2', '', [var4], properties=('hidden',))
@@ -314,7 +314,6 @@ with a hidden option::
 
     >>> print c.od1.var1
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: var1 
     with properties ['hidden']
     >>> c.read_only()
@@ -331,7 +330,6 @@ mode. But in read only mode, an error is raised if no value has been defined::
     >>> c.read_only()
     >>> print c.od1.var2
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: var2 
     with properties ['mandatory']
     >>> c.read_write()
@@ -348,7 +346,6 @@ Let's try to modify a frozen option::
     value
     >>> c.od1.var3 = u'value2'
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: cannot change the value for option var3 this option is frozen
     >>> c.read_only()
     >>> print c.od1.var3
@@ -357,23 +354,21 @@ Let's try to modify a frozen option::
 Tiramisu allows us to use user defined properties. Let's define and use one in 
 read/write or read only mode::
 
-    >>> c.cfgimpl_get_settings().append('inconnu')
+    >>> c.cfgimpl_get_settings().append('unknown')
     >>> print c.od1.var3
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: 
-    var3 with properties ['inconnu']
-    >>> c.cfgimpl_get_settings().remove('inconnu')
+    var3 with properties ['unknown']
+    >>> c.cfgimpl_get_settings().remove('unknown')
     >>> print c.od1.var3
     value 
     
-Properties can also be defined on an option group, (that is, on an 
-:term:`option description`), let's hide a group and try to access to it::
+Properties can also be defined on an option group (that is, on an 
+:term:`option description`) let's hide a group and try to access to it::
 
     >>> c.read_write()
     >>> print c.od2.var4
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 
     with properties ['hidden']
     >>> c.read_only()
@@ -387,7 +382,6 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property
     ['hidden']
     >>> print c.od1.var1
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: 
     var1 with properties ['hidden']
     >>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('hidden')
@@ -400,7 +394,6 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property
     ['hidden']
     >>> print c.od1.var1
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: 
     var1 with properties ['hidden']
 
@@ -438,11 +431,10 @@ A multi-option's value can be manipulated like a list::
     >>> print c.od1.var1
     [u'var1']
 
-But it is not possible to set a value to a multi-option wich is not a list::    
+But it is not possible to set a value to a multi-option which is not a list::    
 
     >>> c.od1.var1 = u'error'
     Traceback (most recent call last):
-    [...]
     ValueError: invalid value error for option var1 which must be a list
     
 
@@ -514,17 +506,14 @@ But it is forbidden to change the lenght of a slave::
     slave2 = [u'default', u'default']
     >>> c.master.slave1 = [u'new1']
     Traceback (most recent call last):
-    [...]
     tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master
     >>> c.master.slave1 = [u'new1', u'new2', u'new3']
-    [...]
     tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master
     
 you have to call the `pop` function on the master::
 
     >>> c.master.master = [u'oui']
     Traceback (most recent call last):
-    [...]
     tiramisu.error.SlaveError: invalid len for the master: master which has slave1 as slave with greater len
     >>> c.master.master.pop(0)
     u'oui'
@@ -570,6 +559,6 @@ Here are the (useful) methods on ``Config`` (or `SubConfig`).
 A :class:`~config.CommonConfig` is a abstract base class. A
 :class:`~config.SubConfig` is an just in time created objects that wraps an
 ::class:`~option.OptionDescription`. A SubConfig differs from a Config in the
-::fact that a config is a root object and has an environnement, a context wich
-::defines the different properties, access rules, vs... There is generally only
-::one Config, and many SubConfigs.
+fact that a config is a root object and has an environnement, a context which
+defines the different properties, access rules, vs... There is generally only
+one Config, and many SubConfigs.
index 4b268ed..9de8748 100644 (file)
@@ -57,7 +57,7 @@ A requirement is a list of dictionaries that have fairly this form::
     'transitive':True, 'same_action': True}]
 
 Actually a transformation is made to this dictionary during the validation of 
-this requires at the :class:`~option.Option()`'s init. The dictionairy becomes 
+this requires at the :class:`~option.Option()`'s init. The dictionary becomes 
 a tuple, wich is passed to the :meth:`~setting.Settings.apply_requires()` 
 method. Take a look at the code to fully understand the exact meaning of the 
 requirements:
@@ -103,7 +103,6 @@ hidden any more::
     ['hidden']
     >>> print c.od1.var1
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: 
     var1 with properties ['hidden']
     >>> c.od1.var2 = u'oui'
@@ -123,7 +122,6 @@ document.)::
     >>> c.od1.var2 = u'non'
     >>> print c.od2.var4
     Traceback (most recent call last):
-    [...]
     tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden']
     >>> c.od1.var2 = u'oui'
     >>> print c.od2.var4
@@ -144,21 +142,20 @@ Requirements can be accumulated for different or identical properties (inverted
 or not)::
 
     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
-        'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
-        'action':'hidden'}])
+    ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
+    ... 'action':'hidden'}])
     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
-        'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', 
-        'action':'disabled', 'inverse':True}])
+    ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', 
+    ... 'action':'disabled', 'inverse':True}])
     
 But it is not possible to have inverted requirements on the same property. 
 Here is an impossible situation::
 
     >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, 
-        'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
-        'hidden', True}])
+    ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', 
+    ... 'hidden', True}])
     
     Traceback (most recent call last):
-    [...]
     ValueError: inconsistency in action types for option: var3 action: hidden
             
 Validation upon a whole configuration object
@@ -184,11 +181,8 @@ Let's define validator (wich is a normal python function)::
 Here is an option wich uses this validator::
 
     >>> var1 = UnicodeOption('var1', '', u'oui', validator=valid_a, validator_args={'letter': 'o'})
-    >>>
     >>> od1 = OptionDescription('od1', '', [var1])
-    >>>
     >>> rootod = OptionDescription('rootod', '', [od1])
-    >>>
     >>> c = Config(rootod)
     >>> c.read_write()
     
@@ -196,11 +190,10 @@ The validation is applied at the modification time::
 
     >>> c.od1.var1 = u'non'
     Traceback (most recent call last):
-    [...]
     ValueError: invalid value non for option var1
     >>> c.od1.var1 = u'oh non'
     
-    Il est possible de désactiver la validation :
+You can disabled this validation::
     
     >>> c.cfgimpl_get_settings().remove('validator')
     >>> c.od1.var1 = u'non'
index 803260e..d717c29 100644 (file)
@@ -23,9 +23,6 @@ option APIs
 others
 ----------
 
-.. automodule:: test.test_config_api
-    :members:
-
 .. automodule:: test.test_mandatory
     :members:
 
index 13cc8f2..a2f6a74 100644 (file)
@@ -14,7 +14,7 @@ introduced...
 What is Tiramisu ?
 ===================
 
-Tiramisu is an options handler and an options controller, wich aims at
+Tiramisu is an options handler and an options controller, which aims at
 producing flexible and fast options access. The main advantages are its access
 rules and the fact that the whole consistency is preserved at any time, see
 :doc:`consistency`. There is of course type and structure validations, but also
@@ -65,7 +65,7 @@ So by now, we have:
 
 - a namespace (which is `c` here)
 - the access of an option's value by the
-  attribute access way (here `bool`, wich is a boolean option 
+  attribute access way (here `bool`, which is a boolean option 
   :class:`~tiramisu.option.BoolOption()`.
 
 So, option objects are produced at the entry point `c` and then handed down to 
index 561c047..51972bd 100644 (file)
@@ -10,7 +10,7 @@
 The tasting of `Tiramisu`
 =========================
 
-.. image:: tiramisu.jpeg
+.. image:: logo.png
    :height: 150px
 
 `Tiramisu`
@@ -34,6 +34,7 @@ controlling options explanations
     getting-started
     config
     option
+    storage
     status
     consistency
     error
diff --git a/doc/logo.png b/doc/logo.png
new file mode 100644 (file)
index 0000000..084a534
Binary files /dev/null and b/doc/logo.png differ
index 90dea0a..47053b8 100644 (file)
@@ -9,7 +9,7 @@ Description of Options
 ----------------------
 
 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 to the option or option description a name and a description document.
 Most constructors take a ``default`` argument that specifies the default
 value of the option. If this argument is not supplied the default value
 is assumed to be ``None``.
@@ -17,7 +17,7 @@ is assumed to be ``None``.
 The `Option` base class
 -------------------------
 
-It's the abstract base class for almost all options (except the symblink).
+It's the abstract base class for almost all options (except the symlink).
 
 .. _optioninit:
 
@@ -28,22 +28,41 @@ It's the abstract base class for almost all options (except the symblink).
 All option types
 ------------------
 
+BoolOption
+~~~~~~~~~~
+
 .. autoclass:: BoolOption
     :private-members:
 
+IntOption
+~~~~~~~~~
+
 .. autoclass:: IntOption
     :private-members:
 
+FloatOption
+~~~~~~~~~~~
+
 .. autoclass:: FloatOption
     :private-members:
 
+StrOption
+~~~~~~~~~
+
 .. autoclass:: StrOption
     :private-members:
 
+UnicodeOption
+~~~~~~~~~~~~~
 
-.. autoclass:: SymLinkOption
+.. autoclass:: UnicodeOption
+    :private-members:
 
-    .. automethod:: __init__
+SymLinkOption
+~~~~~~~~~~~~~
+
+.. autoclass:: SymLinkOption
+    :private-members:
 
 
 ``SymLinkOption`` redirects to another configuration option in the
@@ -52,19 +71,41 @@ configuration, that is :
 - retrieves the value of the target,
 - can set the value of the target too
 
+IPOption
+~~~~~~~~
 
 .. autoclass:: IPOption
+    :private-members:
+
+PortOption
+~~~~~~~~~~
+
+.. autoclass:: PortOption
+    :private-members:
+
+NetmaskOption
+~~~~~~~~~~~~~
 
 .. autoclass:: NetmaskOption
+    :private-members:
+
+NetworkOption
+~~~~~~~~~~~~~
 
 .. autoclass:: NetworkOption
+    :private-members:
+
+DomainnameOption
+~~~~~~~~~~~~~~~~
 
 .. autoclass:: DomainnameOption
+    :private-members:
 
+ChoiceOption
+~~~~~~~~~~~~
 
 .. autoclass:: ChoiceOption
-
-    .. automethod:: __init__
+    :private-members:
 
 
 .. _optdescr:
diff --git a/doc/storage.png b/doc/storage.png
new file mode 100644 (file)
index 0000000..9bef2b3
Binary files /dev/null and b/doc/storage.png differ
diff --git a/doc/storage.svg b/doc/storage.svg
new file mode 100644 (file)
index 0000000..d710cbc
--- /dev/null
@@ -0,0 +1,265 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="400"
+   height="200"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="test.svg"
+   inkscape:export-filename="/home/gnunux/git/tiramisu/doc/storage.png"
+   inkscape:export-xdpi="135"
+   inkscape:export-ydpi="135">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective3827" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="106.95445"
+     inkscape:cy="208.15932"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="841"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-852.36218)">
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 235.5,78.588237 306,109"
+       id="path4403"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4211"
+       inkscape:connection-start-point="d4"
+       transform="translate(0,852.36218)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 235.5,131.08416 305,107"
+       id="path4405"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0"
+       inkscape:connection-start="#g4216"
+       inkscape:connection-start-point="d4"
+       transform="translate(0,852.36218)" />
+    <g
+       id="g4206"
+       transform="translate(-17,590)">
+      <text
+         sodipodi:linespacing="686.00001%"
+         id="text2985"
+         y="368.36218"
+         x="98"
+         style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="368.36218"
+           x="98"
+           id="tspan2987"
+           sodipodi:role="line">Config</tspan></text>
+      <rect
+         y="351.36218"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+    </g>
+    <g
+       id="g4211"
+       transform="translate(-17,590)">
+      <rect
+         y="312.36218"
+         x="189.5"
+         height="30"
+         width="63"
+         id="rect3757-2"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="686.00001%"
+         id="text3777"
+         y="330.36218"
+         x="206"
+         style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="330.36218"
+           x="206"
+           id="tspan3779"
+           sodipodi:role="line">Values</tspan></text>
+    </g>
+    <g
+       id="g4216"
+       transform="translate(-17,590)">
+      <rect
+         y="389.36218"
+         x="189.5"
+         height="30"
+         width="63"
+         id="rect3757-4"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="686.00001%"
+         id="text3799"
+         y="407.36218"
+         x="200"
+         style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           y="407.36218"
+           x="200"
+           id="tspan3801"
+           sodipodi:role="line">Settings</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 127,967.39444 45.5,15.93548"
+       id="path4028"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 127,945.0396 45.5,-16.35484"
+       id="path4030"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none"
+       id="rect4161"
+       width="55.5"
+       height="26"
+       x="277.5"
+       y="946.36218" />
+    <path
+       sodipodi:type="arc"
+       style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path3843"
+       sodipodi:cx="401"
+       sodipodi:cy="334.86218"
+       sodipodi:rx="38"
+       sodipodi:ry="10.5"
+       d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z"
+       transform="matrix(0.71325325,0,0,0.57998971,18.66254,749.17042)" />
+    <path
+       transform="matrix(0.71325325,0,0,0.57998971,18.57337,775.05247)"
+       sodipodi:type="arc"
+       style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path3843-3"
+       sodipodi:cx="401"
+       sodipodi:cy="334.86218"
+       sodipodi:rx="38"
+       sodipodi:ry="10.5"
+       d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z" />
+    <path
+       transform="matrix(0.71325325,0,0,0.57998971,18.52879,762.07519)"
+       sodipodi:type="arc"
+       style="fill:none;stroke:#000000;stroke-width:1.96347165;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path3843-3-0"
+       sodipodi:cx="401"
+       sodipodi:cy="334.86218"
+       sodipodi:rx="38"
+       sodipodi:ry="10.5"
+       d="m 439,334.86218 a 38,10.5 0 1 1 -76,0 38,10.5 0 1 1 76,0 z" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none"
+       id="rect3883"
+       width="62.989182"
+       height="6.7061315"
+       x="274.72043"
+       y="949.91193" />
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none"
+       id="rect3883-3"
+       width="58.087975"
+       height="6.4161367"
+       x="277.34818"
+       y="962.78046" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.26286423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       d="m 277.52869,943.35095 -0.0442,26.02673"
+       id="path3917"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1.26286423;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       d="m 331.64698,969.26909 0.13377,-26.17203"
+       id="path3921"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0" />
+    <text
+       xml:space="preserve"
+       style="font-size:10px;font-style:normal;font-weight:normal;line-height:686.00001335%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+       x="286.33643"
+       y="958.32324"
+       id="text3821"
+       sodipodi:linespacing="686.00001%"><tspan
+         sodipodi:role="line"
+         id="tspan3823"
+         x="286.33643"
+         y="958.32324">Storage</tspan></text>
+    <g
+       id="g4201"
+       transform="translate(-17,590)">
+      <rect
+         y="293.42468"
+         x="81"
+         height="30"
+         width="63"
+         id="rect3757-5"
+         style="fill:none;stroke:#000000;stroke-linejoin:round;stroke-opacity:1" />
+      <text
+         sodipodi:linespacing="100%"
+         id="text4190"
+         y="309.42468"
+         x="110.27588"
+         style="font-size:10px;font-style:normal;font-weight:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+         xml:space="preserve"><tspan
+           id="tspan4194"
+           y="309.42468"
+           x="110.27588"
+           sodipodi:role="line">Option</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 95.5,913.42468 0,27.9375"
+       id="path4199"
+       inkscape:connector-type="polyline"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/doc/storage.txt b/doc/storage.txt
new file mode 100644 (file)
index 0000000..6bfb18c
--- /dev/null
@@ -0,0 +1,52 @@
+Storage
+=======
+
+.. automodule:: tiramisu.storage
+
+.. automethod:: tiramisu.storage.set_storage
+
+.. image:: storage.png
+
+Dictionary
+~~~~~~~~~~
+
+.. automodule:: tiramisu.storage.dictionary
+
+Dictionary settings:
+
+.. automethod:: tiramisu.storage.dictionary.storage.Setting
+
+Sqlite3
+~~~~~~~
+
+.. automodule:: tiramisu.storage.sqlite3
+
+Sqlite3 settings:
+
+.. automethod:: tiramisu.storage.sqlite3.storage.Setting
+
+Example
+~~~~~~~
+
+>>> from tiramisu.option import StrOption, OptionDescription
+>>> from tiramisu.config import Config
+>>> from tiramisu.storage import set_storage
+>>> set_storage('sqlite3', dir_database='/tmp/tiramisu')
+>>> s = StrOption('str', '')
+>>> o = OptionDescription('od', '', [s])
+>>> c1 = Config(o, persistent=True, session_id='xxxx')
+>>> c1.str
+>>> c1.str = 'yes'
+>>> c1.str
+'yes'
+>>> del(c1)
+>>> c2 = Config(o, persistent=True, session_id='xxxx')
+>>> c2.str
+'yes'
+>>> del(c2)
+>>> list_sessions()
+['xxxx']
+>>> delete_session('xxxx')
+>>> c3 = Config(o, persistent=True, session_id='xxxx')
+>>> c3.str
+
diff --git a/doc/tiramisu.jpeg b/doc/tiramisu.jpeg
deleted file mode 100644 (file)
index 0b5c4ae..0000000
Binary files a/doc/tiramisu.jpeg and /dev/null differ
index 2b57b1c..de30995 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -1,53 +1,63 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 from distutils.core import setup
-from os.path import dirname, abspath, join, normpath, isdir, basename
+from os.path import dirname, abspath, join, normpath, isdir
 from os import listdir
 
-import os
-import subprocess
 
 def fetch_version():
-    """Get version from version.in or latest git tag"""
-    version_file='version.in'
-    version = "1.0"
-    git_last_tag_cmd = ['git', 'describe', '--tags', '--abbrev=0']
-
-    try:
-        if os.path.isfile(version_file):
-            version=file(version_file).readline().strip()
-        elif os.path.isdir('.git'):
-            popen = subprocess.Popen(git_last_tag_cmd, stdout=subprocess.PIPE)
-            out, ret = popen.communicate()
-            for line in out.split('\n'):
-                if line:
-                    version = line.lstrip('release/')
-                    break
-    except OSError:
-        pass # Failing is fine, we just can't print the version then
-
-    return version
+    """Get version from version.in"""
+    return file('VERSION', 'r').readline().strip()
+
 
 def return_storages():
     "returns all the storage plugins that are living in tiramisu/storage"
     here = dirname(abspath(__file__))
     storages_path = normpath(join(here, 'tiramisu', 'storage'))
-    dir_content = [ content for content in listdir(storages_path) \
-                    if not content =='__pycache__']
-    storages = filter(isdir, [join(storages_path, content) \
+    dir_content = [content for content in listdir(storages_path)
+                   if not content == '__pycache__']
+    storages = filter(isdir, [join(storages_path, content)
                       for content in dir_content])
-    storage_list = [basename(storage) for storage in storages]
+    storage_list = ['.'.join(storage.split('/')[-3:]) for storage in storages]
     return storage_list
 
+
 packages = ['tiramisu', 'tiramisu.storage']
 packages.extend(return_storages())
-
 setup(
-    author='cadoles team',
+    author="Tiramisu's team",
     author_email='contact@cadoles.com',
     name='tiramisu',
     version=fetch_version(),
-    description='configuration management tool',
-    url='http://labs.libre-entreprise.org/projects/tiramisu',
+    description='an options controller tool',
+    url='http://tiramisu.labs.libre-entreprise.org/',
+    classifiers=[
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 3",
+        "Development Status :: 4 - Beta",
+        "Environment :: Other Environment",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+        "Operating System :: OS Independent",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Text Processing :: Linguistic"
+    ],
+    long_description="""\
+An options controller tool
+-------------------------------------
+
+Due to more and more available options required to set up an operating system,
+compiler options or whatever, it became quite annoying to hand the necessary
+options to where they are actually used and even more annoying to add new
+options. To circumvent these problems the configuration control was
+introduced...
+
+Tiramisu is an options handler and an options controller, wich aims at
+producing flexible and fast options access.
+
+
+This version requires Python 2.6 or later.
+""",
     packages=packages
 )
index 47270ee..f483c76 100644 (file)
@@ -4,7 +4,7 @@ from tiramisu import setting
 setting.expires_time = 1
 from tiramisu.option import IntOption, OptionDescription
 from tiramisu.config import Config
-from time import sleep
+from time import sleep, time
 
 
 def make_description():
@@ -20,13 +20,38 @@ def test_cache():
     values = c.cfgimpl_get_values()
     settings = c.cfgimpl_get_settings()
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u2
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
-    assert 'u2' in values._p_.get_cached('value', c)
-    assert 'u2' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
+
+
+def test_get_cache():
+    # force a value in cache, try if reget corrupted value
+    od1 = make_description()
+    c = Config(od1)
+    values = c.cfgimpl_get_values()
+    settings = c.cfgimpl_get_settings()
+    ntime = time() + 1
+    settings._p_.setcache('u1', set(['inject']), ntime)
+    assert 'inject' in settings[od1.u1]
+    values._p_.setcache('u1', 100, ntime)
+    assert c.u1 == [100]
+
+
+def test_get_cache_no_expire():
+    # force a value in cache, try if reget corrupted value
+    od1 = make_description()
+    c = Config(od1)
+    values = c.cfgimpl_get_values()
+    settings = c.cfgimpl_get_settings()
+    settings._p_.setcache('u1', set(['inject2']), None)
+    assert 'inject2' in settings[od1.u1]
+    values._p_.setcache('u1', 200, None)
+    assert c.u1 == [200]
 
 
 def test_cache_reset():
@@ -36,44 +61,44 @@ def test_cache_reset():
     settings = c.cfgimpl_get_settings()
     #when change a value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u2 = 1
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when remove a value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     del(c.u2)
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when add/del property
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_get_settings()[od1.u2].append('test')
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_get_settings()[od1.u2].remove('test')
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when enable/disabled property
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_get_settings().append('test')
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_get_settings().remove('test')
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
 
 
 def test_cache_reset_multi():
@@ -83,32 +108,32 @@ def test_cache_reset_multi():
     settings = c.cfgimpl_get_settings()
     #when change a value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u3 = [1]
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when append value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u3.append(1)
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when pop value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u3.pop(1)
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
     #when remove a value
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     del(c.u3)
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
 
 
 def test_reset_cache():
@@ -117,23 +142,25 @@ def test_reset_cache():
     values = c.cfgimpl_get_values()
     settings = c.cfgimpl_get_settings()
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache()
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
+    c.u1
+    sleep(1)
     c.u1
     sleep(1)
     c.u2
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
-    assert 'u2' in values._p_.get_cached('value', c)
-    assert 'u2' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache()
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
-    assert 'u2' not in values._p_.get_cached('value', c)
-    assert 'u2' not in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
+    assert 'u2' not in values._p_.get_cached(c)
+    assert 'u2' not in settings._p_.get_cached(c)
 
 
 def test_reset_cache_subconfig():
@@ -142,9 +169,9 @@ def test_reset_cache_subconfig():
     c = Config(od2)
     values = c.cfgimpl_get_values()
     c.od1.u1
-    assert 'od1.u1' in values._p_.get_cached('value', c)
+    assert 'od1.u1' in values._p_.get_cached(c)
     c.od1.cfgimpl_reset_cache()
-    assert 'od1.u1' not in values._p_.get_cached('value', c)
+    assert 'od1.u1' not in values._p_.get_cached(c)
 
 
 def test_reset_cache_only_expired():
@@ -153,22 +180,60 @@ def test_reset_cache_only_expired():
     values = c.cfgimpl_get_values()
     settings = c.cfgimpl_get_settings()
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    c.cfgimpl_reset_cache(True)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    sleep(1)
+    c.u1
+    sleep(1)
+    c.u2
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
+    c.cfgimpl_reset_cache(True)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
+
+
+def test_cache_not_expire():
+    od1 = make_description()
+    c = Config(od1)
+    values = c.cfgimpl_get_values()
+    settings = c.cfgimpl_get_settings()
+    settings.remove('expire')
+    c.u1
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache(True)
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     sleep(1)
     c.u2
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
-    assert 'u2' in values._p_.get_cached('value', c)
-    assert 'u2' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache(True)
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
-    assert 'u2' in values._p_.get_cached('value', c)
-    assert 'u2' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
+    assert 'u2' in values._p_.get_cached(c)
+    assert 'u2' in settings._p_.get_cached(c)
+
+
+def test_cache_not_cache():
+    od1 = make_description()
+    c = Config(od1)
+    values = c.cfgimpl_get_values()
+    settings = c.cfgimpl_get_settings()
+    settings.remove('cache')
+    c.u1
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
 
 
 def test_reset_cache_only():
@@ -177,14 +242,14 @@ def test_reset_cache_only():
     values = c.cfgimpl_get_values()
     settings = c.cfgimpl_get_settings()
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache(only=('values',))
-    assert 'u1' not in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' not in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.u1
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' in settings._p_.get_cached(c)
     c.cfgimpl_reset_cache(only=('settings',))
-    assert 'u1' in values._p_.get_cached('value', c)
-    assert 'u1' not in settings._p_.get_cached('property', c)
+    assert 'u1' in values._p_.get_cached(c)
+    assert 'u1' not in settings._p_.get_cached(c)
index 4e38f19..17e863a 100644 (file)
@@ -141,6 +141,7 @@ def test_information_config():
     string = 'some informations'
     config.impl_set_information('info', string)
     assert config.impl_get_information('info') == string
+    raises(ValueError, "config.impl_get_information('noinfo')")
 
 
 def test_config_impl_get_path_by_opt():
index 62a74c0..ab4b484 100644 (file)
@@ -19,7 +19,6 @@ def make_description():
     boolop = BoolOption('boolop', 'Test boolean option op', default=True)
     wantref_option = BoolOption('wantref', 'Tests', default=False)
     wantframework_option = BoolOption('wantframework', 'Test', default=False)
-
     gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
     descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
                                                wantref_option, stroption,
@@ -117,6 +116,23 @@ def test_find_in_config():
     #assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy')
 
 
+def test_find_multi():
+    b = BoolOption('bool', '', multi=True)
+    o = OptionDescription('od', '', [b])
+    conf = Config(o)
+    raises(AttributeError, "conf.find(byvalue=True)")
+    raises(AttributeError, "conf.find_first(byvalue=True)")
+    conf.bool.append(False)
+    raises(AttributeError, "conf.find(byvalue=True)")
+    raises(AttributeError, "conf.find_first(byvalue=True)")
+    conf.bool.append(False)
+    raises(AttributeError, "conf.find(byvalue=True)")
+    raises(AttributeError, "conf.find_first(byvalue=True)")
+    conf.bool.append(True)
+    assert conf.find(byvalue=True) == [b]
+    assert conf.find_first(byvalue=True) == b
+
+
 def test_does_not_find_in_config():
     descr = make_description()
     conf = Config(descr)
index e889c92..2bdba53 100644 (file)
@@ -29,6 +29,15 @@ def test_ip_default():
     c.a == '88.88.88.88'
 
 
+def test_ip_reserved():
+    a = IPOption('a', '')
+    b = IPOption('b', '', allow_reserved=True)
+    od = OptionDescription('od', '', [a, b])
+    c = Config(od)
+    raises(ValueError, "c.a = '226.94.1.1'")
+    c.b = '226.94.1.1'
+
+
 def test_network():
     a = NetworkOption('a', '')
     od = OptionDescription('od', '', [a])
index 0266855..8ca8687 100644 (file)
@@ -4,19 +4,27 @@ from py.test import raises
 from tiramisu.setting import groups
 from tiramisu.config import Config
 from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
-    StrOption, OptionDescription
-from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError
+    StrOption, OptionDescription, SymLinkOption
+from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError
 
 
 def return_val():
     return 'val'
 
 
-def return_list():
+def return_concat(*args):
+    return '.'.join(list(args))
+
+
+def return_list(value=None):
     return ['val', 'val']
 
 
-def return_value(value):
+def return_list2(*args):
+    return list(args)
+
+
+def return_value(value=None):
     return value
 
 
@@ -298,18 +306,73 @@ def test_callback():
 
 def test_callback_value():
     val1 = StrOption('val1', "", 'val')
-    val2 = StrOption('val2', "", callback=return_value, callback_params={'': (('val1', False),)})
-    maconfig = OptionDescription('rootconfig', '', [val1, val2])
+    val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)})
+    val3 = StrOption('val3', "", callback=return_value, callback_params={'': ('yes',)})
+    val4 = StrOption('val4', "", callback=return_value, callback_params={'value': ((val1, False),)})
+    val5 = StrOption('val5', "", callback=return_value, callback_params={'value': ('yes',)})
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5])
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1 == 'val'
     assert cfg.val2 == 'val'
+    assert cfg.val4 == 'val'
     cfg.val1 = 'new-val'
     assert cfg.val1 == 'new-val'
     assert cfg.val2 == 'new-val'
+    assert cfg.val4 == 'new-val'
     del(cfg.val1)
     assert cfg.val1 == 'val'
     assert cfg.val2 == 'val'
+    assert cfg.val3 == 'yes'
+    assert cfg.val4 == 'val'
+    assert cfg.val5 == 'yes'
+
+
+def test_callback_value_tuple():
+    val1 = StrOption('val1', "", 'val1')
+    val2 = StrOption('val2', "", 'val2')
+    val3 = StrOption('val3', "", callback=return_concat, callback_params={'': ((val1, False), (val2, False))})
+    val4 = StrOption('val4', "", callback=return_concat, callback_params={'': ('yes', 'no')})
+    raises(ValueError, "StrOption('val4', '', callback=return_concat, callback_params={'value': ('yes', 'no')})")
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val1 == 'val1'
+    assert cfg.val2 == 'val2'
+    assert cfg.val3 == 'val1.val2'
+    assert cfg.val4 == 'yes.no'
+    cfg.val1 = 'new-val'
+    assert cfg.val3 == 'new-val.val2'
+    del(cfg.val1)
+    assert cfg.val3 == 'val1.val2'
+
+
+def test_callback_value_force_permissive():
+    val1 = StrOption('val1', "", 'val', properties=('disabled',))
+    val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)})
+    val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val1, True),)})
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3])
+    cfg = Config(maconfig)
+    cfg.read_only()
+    raises(ConfigError, "cfg.val2")
+    assert cfg.val3 is None
+
+
+def test_callback_symlink():
+    val1 = StrOption('val1', "", 'val')
+    val2 = SymLinkOption('val2', val1)
+    val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val2, False),)})
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val1 == 'val'
+    assert cfg.val3 == 'val'
+    cfg.val1 = 'new-val'
+    assert cfg.val1 == 'new-val'
+    assert cfg.val3 == 'new-val'
+    del(cfg.val1)
+    assert cfg.val1 == 'val'
+    assert cfg.val3 == 'val'
 
 
 def test_callback_list():
@@ -336,21 +399,28 @@ def test_callback_multi():
 
 def test_callback_multi_value():
     val1 = StrOption('val1', "", ['val'], multi=True)
-    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1', False),)})
-    maconfig = OptionDescription('rootconfig', '', [val1, val2])
+    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
+    val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
+    val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), 'yes')})
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4])
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1 == ['val']
     assert cfg.val2 == ['val']
+    assert cfg.val4 == ['val', 'yes']
     cfg.val1 = ['new-val']
     assert cfg.val1 == ['new-val']
     assert cfg.val2 == ['new-val']
+    assert cfg.val4 == ['new-val', 'yes']
     cfg.val1.append('new-val2')
     assert cfg.val1 == ['new-val', 'new-val2']
     assert cfg.val2 == ['new-val', 'new-val2']
+    assert cfg.val4 == ['new-val', 'yes', 'new-val2', 'yes']
     del(cfg.val1)
     assert cfg.val1 == ['val']
     assert cfg.val2 == ['val']
+    assert cfg.val3 == ['yes']
+    assert cfg.val4 == ['val', 'yes']
 
 
 def test_callback_multi_list():
@@ -455,41 +525,67 @@ def test_callback_master_and_slaves_slave_list():
 
 def test_callback_master_and_slaves_value():
     val1 = StrOption('val1', "", multi=True)
-    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1.val1', False),)})
-    interface1 = OptionDescription('val1', '', [val1, val2])
+    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
+    val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
+    val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
+    val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
+    interface1 = OptionDescription('val1', '', [val1, val2, val3, val5])
     interface1.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [interface1])
+    maconfig = OptionDescription('rootconfig', '', [interface1, val4])
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1.val1 == []
     assert cfg.val1.val2 == []
+    assert cfg.val1.val3 == []
+    assert cfg.val1.val5 == []
     #
     cfg.val1.val1 = ['val1']
     assert cfg.val1.val1 == ['val1']
     assert cfg.val1.val2 == ['val1']
+    assert cfg.val1.val3 == ['yes']
+    assert cfg.val1.val5 == ['val10']
     #
     cfg.val1.val1.append('val2')
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val1', 'val2']
+    assert cfg.val1.val3 == ['yes', 'yes']
+    assert cfg.val1.val5 == ['val10', 'val11']
     #
     cfg.val1.val1 = ['val1', 'val2', 'val3']
     assert cfg.val1.val1 == ['val1', 'val2', 'val3']
     assert cfg.val1.val2 == ['val1', 'val2', 'val3']
+    assert cfg.val1.val3 == ['yes', 'yes', 'yes']
+    assert cfg.val1.val5 == ['val10', 'val11', None]
     #
     cfg.val1.val1.pop(2)
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val1', 'val2']
+    assert cfg.val1.val3 == ['yes', 'yes']
+    assert cfg.val1.val5 == ['val10', 'val11']
     #
     cfg.val1.val2 = ['val2', 'val2']
+    cfg.val1.val3 = ['val2', 'val2']
+    cfg.val1.val5 = ['val2', 'val2']
     assert cfg.val1.val2 == ['val2', 'val2']
+    assert cfg.val1.val3 == ['val2', 'val2']
+    assert cfg.val1.val5 == ['val2', 'val2']
     #
     cfg.val1.val1.append('val3')
     assert cfg.val1.val2 == ['val2', 'val2', 'val3']
+    assert cfg.val1.val3 == ['val2', 'val2', 'yes']
+    assert cfg.val1.val5 == ['val2', 'val2', None]
+    cfg.cfgimpl_get_settings().remove('cache')
+    cfg.val4 = ['val10', 'val11', 'val12']
+    #if value is already set, not updated !
+    cfg.val1.val1.pop(2)
+    cfg.val1.val1.append('val3')
+    cfg.val1.val1 = ['val1', 'val2', 'val3']
+    assert cfg.val1.val5 == ['val2', 'val2', 'val12']
 
 
 def test_callback_hidden():
     opt1 = BoolOption('opt1', '')
-    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': (('od1.opt1', False),)})
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
     od1 = OptionDescription('od1', '', [opt1], properties=('hidden',))
     od2 = OptionDescription('od2', '', [opt2])
     maconfig = OptionDescription('rootconfig', '', [od1, od2])
@@ -498,3 +594,84 @@ def test_callback_hidden():
     cfg.read_write()
     raises(PropertiesOptionError, 'cfg.od1.opt1')
     cfg.od2.opt2
+
+
+def test_callback_two_disabled():
+    opt1 = BoolOption('opt1', '', properties=('disabled',))
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',))
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(PropertiesOptionError, 'cfg.od2.opt2')
+
+
+def test_callback_calculating_disabled():
+    opt1 = BoolOption('opt1', '', properties=('disabled',))
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(ConfigError, 'cfg.od2.opt2')
+
+
+def test_callback_calculating_mandatory():
+    opt1 = BoolOption('opt1', '', properties=('disabled',))
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',))
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_only()
+    raises(ConfigError, 'cfg.od2.opt2')
+
+
+def test_callback_two_disabled_multi():
+    opt1 = BoolOption('opt1', '', properties=('disabled',))
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True)
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(PropertiesOptionError, 'cfg.od2.opt2')
+
+
+def test_callback_multi_list_params():
+    val1 = StrOption('val1', "", multi=True, default=['val1', 'val2'])
+    val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'': ((val1, False),)})
+    oval2 = OptionDescription('val2', '', [val2])
+    maconfig = OptionDescription('rootconfig', '', [val1, oval2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val2.val2 == ['val', 'val', 'val', 'val']
+
+
+def test_callback_multi_list_params_key():
+    val1 = StrOption('val1', "", multi=True, default=['val1', 'val2'])
+    val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'value': ((val1, False),)})
+    oval2 = OptionDescription('val2', '', [val2])
+    maconfig = OptionDescription('rootconfig', '', [val1, oval2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val2.val2 == ['val', 'val', 'val', 'val']
+
+
+def test_callback_multi_multi():
+    val1 = StrOption('val1', "", multi=True, default=['val1', 'val2', 'val3'])
+    val2 = StrOption('val2', "", multi=True, default=['val11', 'val12'])
+    val3 = StrOption('val3', "", default='val4')
+    val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val2, False))})
+    val5 = StrOption('val5', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val3, False))})
+    val6 = StrOption('val6', "", multi=True, default=['val21', 'val22', 'val23'])
+    val7 = StrOption('val7', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val6, False))})
+    raises(ValueError, "StrOption('val8', '', multi=True, callback=return_list2, callback_params={'value': ((val1, False), (val6, False))})")
+    maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5, val6, val7])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(ConfigError, "cfg.val4")
+    assert cfg.val5 == ['val1', 'val4', 'val2', 'val4', 'val3', 'val4']
+    assert cfg.val7 == ['val1', 'val21', 'val2', 'val22', 'val3', 'val23']
index 8dde2a8..5cf53cd 100644 (file)
@@ -4,7 +4,7 @@ from py.test import raises
 from tiramisu.setting import owners, groups
 from tiramisu.config import Config
 from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
-    OptionDescription
+    SymLinkOption, OptionDescription
 
 
 def test_consistency_not_equal():
@@ -22,6 +22,16 @@ def test_consistency_not_equal():
     c.b = 2
 
 
+def test_consistency_not_equal_symlink():
+    a = IntOption('a', '')
+    b = IntOption('b', '')
+    c = SymLinkOption('c', a)
+    od = OptionDescription('od', '', [a, b, c])
+    a.impl_add_consistency('not_equal', b)
+    c = Config(od)
+    assert set(od._consistencies.keys()) == set([a, b])
+
+
 def test_consistency_not_equal_multi():
     a = IntOption('a', '', multi=True)
     b = IntOption('b', '', multi=True)
index a841b9b..ed5f7d7 100644 (file)
@@ -327,30 +327,29 @@ def test_reset_properties():
     cfg = Config(descr)
     setting = cfg.cfgimpl_get_settings()
     option = cfg.cfgimpl_get_description().gc.dummy
-    assert setting._p_.get_properties(cfg) == {}
+    assert setting._p_.get_modified_properties() == {}
     setting.append('frozen')
-    assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))}
+    assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'cache', 'validator'))}
     setting.reset()
-    assert setting._p_.get_properties(cfg) == {}
+    assert setting._p_.get_modified_properties() == {}
     setting[option].append('test')
-    assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))}
+    assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))}
     setting.reset()
-    assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))}
+    assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))}
     setting.append('frozen')
-    assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))}
+    assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))}
     setting.reset(option)
-    assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))}
+    assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache'))}
     setting[option].append('test')
-    assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))}
+    assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))}
     setting.reset(all_properties=True)
-    assert setting._p_.get_properties(cfg) == {}
+    assert setting._p_.get_modified_properties() == {}
     raises(ValueError, 'setting.reset(all_properties=True, opt=option)')
     a = descr.wantref
     setting[a].append('test')
     setting[a].reset()
 
 
-
 def test_reset_multiple():
     descr = make_description()
     cfg = Config(descr)
diff --git a/test/test_option_validator.py b/test/test_option_validator.py
new file mode 100644 (file)
index 0000000..8e00916
--- /dev/null
@@ -0,0 +1,59 @@
+import autopath
+from py.test import raises
+
+from tiramisu.config import Config
+from tiramisu.option import StrOption, OptionDescription
+from tiramisu.error import ConfigError
+
+
+def return_true(value, param=None):
+    if value == 'val' and param in [None, 'yes']:
+        return True
+
+
+def return_false(value, param=None):
+    if value == 'val' and param in [None, 'yes']:
+        return False
+
+
+def return_val(value, param=None):
+    return 'val'
+
+
+def test_validator():
+    opt1 = StrOption('opt1', '', validator=return_true, default='val')
+    raises(ValueError, "StrOption('opt2', '', validator=return_false, default='val')")
+    raises(ConfigError, "StrOption('opt3', '', validator=return_val, default='val')")
+    opt2 = StrOption('opt2', '', validator=return_false)
+    opt3 = StrOption('opt3', '', validator=return_val)
+    root = OptionDescription('root', '', [opt1, opt2, opt3])
+    cfg = Config(root)
+    assert cfg.opt1 == 'val'
+    raises(ValueError, "cfg.opt2 = 'val'")
+    raises(ConfigError, "cfg.opt3 = 'val'")
+
+
+def test_validator_params():
+    opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ('yes',)}, default='val')
+    raises(ValueError, "StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}, default='val')")
+    raises(ConfigError, "StrOption('opt3', '', validator=return_val, validator_params={'': ('yes',)}, default='val')")
+    opt2 = StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)})
+    opt3 = StrOption('opt3', '', validator=return_val, validator_params={'': ('yes',)})
+    root = OptionDescription('root', '', [opt1, opt2, opt3])
+    cfg = Config(root)
+    assert cfg.opt1 == 'val'
+    raises(ValueError, "cfg.opt2 = 'val'")
+    raises(ConfigError, "cfg.opt3 = 'val'")
+
+
+def test_validator_params_key():
+    opt1 = StrOption('opt1', '', validator=return_true, validator_params={'param': ('yes',)}, default='val')
+    raises(TypeError, "StrOption('opt2', '', validator=return_true, validator_params={'param_unknown': ('yes',)}, default='val')")
+    root = OptionDescription('root', '', [opt1])
+    cfg = Config(root)
+    assert cfg.opt1 == 'val'
+
+
+def test_validator_params_option():
+    opt0 = StrOption('opt0', '', default='val')
+    raises(ValueError, "opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ((opt0, False),)}, default='val')")
index 7b9dffe..7ecd860 100644 (file)
@@ -64,9 +64,9 @@ def test_make_dict_filter():
     config = Config(descr)
     config.read_write()
     subresult = {'numero_etab': None, 'nombre_interfaces': 1,
-              'serveur_ntp': [], 'mode_conteneur_actif': False,
-              'time_zone': 'Paris', 'nom_machine': 'eoleng',
-              'activer_proxy_client': False}
+                 'serveur_ntp': [], 'mode_conteneur_actif': False,
+                 'time_zone': 'Paris', 'nom_machine': 'eoleng',
+                 'activer_proxy_client': False}
     result = {}
     for key, value in subresult.items():
         result['general.' + key] = value
@@ -114,7 +114,6 @@ def test_iter_not_group():
     raises(TypeError, "list(config.iter_groups(group_type='family'))")
 
 
-
 def test_groups_with_master():
     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
@@ -252,6 +251,22 @@ def test_values_with_master_and_slaves_master():
     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
 
 
+def test_values_with_master_and_slaves_master_error():
+    ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
+    netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
+    interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
+    interface1.impl_set_group_type(groups.master)
+    maconfig = OptionDescription('toto', '', [interface1])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"]
+    raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
+    raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
+    cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']
+    raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
+    raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
+
+
 def test_values_with_master_owner():
     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
index 0f9af61..ba6cf3f 100644 (file)
@@ -88,6 +88,64 @@ def test_multiple_requires():
     c.ip_address_service
 
 
+def test_multiple_requires_cumulative():
+    a = StrOption('activate_service', '')
+    b = IPOption('ip_address_service', '',
+                 requires=[{'option': a, 'expected': 'yes', 'action': 'disabled'},
+                           {'option': a, 'expected': 'yes', 'action': 'hidden'}])
+    od = OptionDescription('service', '', [a, b])
+    c = Config(od)
+    c.read_write()
+    c.ip_address_service
+    c.activate_service = 'yes'
+    props = []
+    try:
+        c.ip_address_service
+    except PropertiesOptionError as err:
+        props = err.proptype
+    assert set(props) == set(['hidden', 'disabled'])
+
+    c.activate_service = 'ok'
+    c.ip_address_service
+
+    c.activate_service = 'no'
+    c.ip_address_service
+
+
+def test_multiple_requires_cumulative_inverse():
+    a = StrOption('activate_service', '')
+    b = IPOption('ip_address_service', '',
+                 requires=[{'option': a, 'expected': 'yes', 'action': 'disabled', 'inverse': True},
+                           {'option': a, 'expected': 'yes', 'action': 'hidden', 'inverse': True}])
+    od = OptionDescription('service', '', [a, b])
+    c = Config(od)
+    c.read_write()
+    props = []
+    try:
+        c.ip_address_service
+    except PropertiesOptionError as err:
+        props = err.proptype
+    assert set(props) == set(['hidden', 'disabled'])
+    c.activate_service = 'yes'
+    c.ip_address_service
+
+    c.activate_service = 'ok'
+    props = []
+    try:
+        c.ip_address_service
+    except PropertiesOptionError as err:
+        props = err.proptype
+    assert set(props) == set(['hidden', 'disabled'])
+
+    c.activate_service = 'no'
+    props = []
+    try:
+        c.ip_address_service
+    except PropertiesOptionError as err:
+        props = err.proptype
+    assert set(props) == set(['hidden', 'disabled'])
+
+
 def test_multiple_requires_inverse():
     a = StrOption('activate_service', '')
     b = IPOption('ip_address_service', '',
@@ -476,3 +534,9 @@ def test_set_item():
     c = Config(od)
     c.read_write()
     raises(ValueError, 'c.cfgimpl_get_settings()[a] = ("test",)')
+
+
+def test_properties_conflict():
+    a = BoolOption('activate_service', '', True)
+    raises(ValueError, "IPOption('ip_address_service', '', properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])")
+    raises(ValueError, "od1 = OptionDescription('service', '', [a], properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])")
index 0104e84..1f2aee6 100644 (file)
@@ -4,10 +4,13 @@ from py.test import raises
 
 from tiramisu.config import Config, SubConfig
 from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
-    StrOption, OptionDescription, SymLinkOption, UnicodeOption
+    StrOption, SymLinkOption, UnicodeOption, IPOption, OptionDescription, \
+    PortOption, NetworkOption, NetmaskOption, DomainnameOption
 
 
 def test_slots_option():
+    c = ChoiceOption('a', '', ('a',))
+    raises(AttributeError, "c.x = 1")
     c = BoolOption('a', '')
     raises(AttributeError, "c.x = 1")
     c = IntOption('a', '')
@@ -20,10 +23,96 @@ def test_slots_option():
     raises(AttributeError, "c.x = 1")
     c = UnicodeOption('a', '')
     raises(AttributeError, "c.x = 1")
-    c = ChoiceOption('a', '', ('a',))
+    c = IPOption('a', '')
     raises(AttributeError, "c.x = 1")
     c = OptionDescription('a', '', [])
     raises(AttributeError, "c.x = 1")
+    c = PortOption('a', '')
+    raises(AttributeError, "c.x = 1")
+    c = NetworkOption('a', '')
+    raises(AttributeError, "c.x = 1")
+    c = NetmaskOption('a', '')
+    raises(AttributeError, "c.x = 1")
+    c = DomainnameOption('a', '')
+    raises(AttributeError, "c.x = 1")
+
+
+def test_slots_option_readonly():
+    a = ChoiceOption('a', '', ('a',))
+    b = BoolOption('b', '')
+    c = IntOption('c', '')
+    d = FloatOption('d', '')
+    e = StrOption('e', '')
+    g = UnicodeOption('g', '')
+    h = IPOption('h', '')
+    i = PortOption('i', '')
+    j = NetworkOption('j', '')
+    k = NetmaskOption('k', '')
+    l = DomainnameOption('l', '')
+    m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l])
+    a._requires = 'a'
+    b._requires = 'b'
+    c._requires = 'c'
+    d._requires = 'd'
+    e._requires = 'e'
+    g._requires = 'g'
+    h._requires = 'h'
+    i._requires = 'i'
+    j._requires = 'j'
+    k._requires = 'k'
+    l._requires = 'l'
+    m._requires = 'm'
+    Config(m)
+    raises(AttributeError, "a._requires = 'a'")
+    raises(AttributeError, "b._requires = 'b'")
+    raises(AttributeError, "c._requires = 'c'")
+    raises(AttributeError, "d._requires = 'd'")
+    raises(AttributeError, "e._requires = 'e'")
+    raises(AttributeError, "g._requires = 'g'")
+    raises(AttributeError, "h._requires = 'h'")
+    raises(AttributeError, "i._requires = 'i'")
+    raises(AttributeError, "j._requires = 'j'")
+    raises(AttributeError, "k._requires = 'k'")
+    raises(AttributeError, "l._requires = 'l'")
+    raises(AttributeError, "m._requires = 'm'")
+
+
+def test_slots_option_readonly_name():
+    a = ChoiceOption('a', '', ('a',))
+    b = BoolOption('b', '')
+    c = IntOption('c', '')
+    d = FloatOption('d', '')
+    e = StrOption('e', '')
+    f = SymLinkOption('f', c)
+    g = UnicodeOption('g', '')
+    h = IPOption('h', '')
+    i = PortOption('i', '')
+    j = NetworkOption('j', '')
+    k = NetmaskOption('k', '')
+    l = DomainnameOption('l', '')
+    m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l])
+    raises(AttributeError, "a._name = 'a'")
+    raises(AttributeError, "b._name = 'b'")
+    raises(AttributeError, "c._name = 'c'")
+    raises(AttributeError, "d._name = 'd'")
+    raises(AttributeError, "e._name = 'e'")
+    raises(AttributeError, "f._name = 'f'")
+    raises(AttributeError, "g._name = 'g'")
+    raises(AttributeError, "h._name = 'h'")
+    raises(AttributeError, "i._name = 'i'")
+    raises(AttributeError, "j._name = 'j'")
+    raises(AttributeError, "k._name = 'k'")
+    raises(AttributeError, "l._name = 'l'")
+    raises(AttributeError, "m._name = 'm'")
+
+
+def test_slots_description():
+    # __slots__ for OptionDescription should be complete for __getattr__
+    slots = set()
+    for subclass in OptionDescription.__mro__:
+        if subclass is not object:
+            slots.update(subclass.__slots__)
+    assert slots == set(OptionDescription.__slots__)
 
 
 def test_slots_config():
diff --git a/test/test_state.py b/test/test_state.py
new file mode 100644 (file)
index 0000000..8587b4a
--- /dev/null
@@ -0,0 +1,250 @@
+from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \
+    OptionDescription
+from tiramisu.config import Config
+from tiramisu.setting import owners
+from tiramisu.storage import delete_session
+from tiramisu.error import ConfigError
+from pickle import dumps, loads
+
+
+def return_value(value=None):
+        return value
+
+
+def _get_slots(opt):
+    slots = set()
+    for subclass in opt.__class__.__mro__:
+        if subclass is not object:
+            slots.update(subclass.__slots__)
+    return slots
+
+
+def _no_state(opt):
+    for attr in _get_slots(opt):
+        if 'state' in attr:
+            try:
+                getattr(opt, attr)
+            except:
+                pass
+            else:
+                raise Exception('opt should have already attribute {0}'.format(attr))
+
+
+def _diff_opt(opt1, opt2):
+    attr1 = set(_get_slots(opt1))
+    attr2 = set(_get_slots(opt2))
+    diff1 = attr1 - attr2
+    diff2 = attr2 - attr1
+    if diff1 != set():
+        raise Exception('more attribute in opt1 {0}'.format(list(diff1)))
+    if diff2 != set():
+        raise Exception('more attribute in opt2 {0}'.format(list(diff2)))
+    for attr in attr1:
+        if attr in ['_cache_paths']:
+            continue
+        err1 = False
+        err2 = False
+        val1 = None
+        val2 = None
+        try:
+            val1 = getattr(opt1, attr)
+        except:
+            err1 = True
+
+        try:
+            val2 = getattr(opt2, attr)
+        except:
+            err2 = True
+        assert err1 == err2
+        if val1 is None:
+            assert val1 == val2
+        elif attr == '_children':
+            assert val1[0] == val2[0]
+            for index, _opt in enumerate(val1[1]):
+                assert _opt._name == val2[1][index]._name
+        elif attr == '_requires':
+            assert val1[0][0][0]._name == val2[0][0][0]._name
+            assert val1[0][0][1:] == val2[0][0][1:]
+        elif attr == '_opt':
+            assert val1._name == val2._name
+        elif attr == '_consistencies':
+            # dict is only a cache
+            if isinstance(val1, list):
+                for index, consistency in enumerate(val1):
+                    assert consistency[0] == val2[index][0]
+                    assert consistency[1]._name == val2[index][1]._name
+        elif attr == '_callback':
+            assert val1[0] == val2[0]
+            if val1[1] is not None:
+                for key, values in val1[1].items():
+                    for idx, value in enumerate(values):
+                        if isinstance(value, tuple):
+                            assert val1[1][key][idx][0]._name == val2[1][key][idx][0]._name
+                            assert val1[1][key][idx][1] == val2[1][key][idx][1]
+                        else:
+                            assert val1[1][key][idx] == val2[1][key][idx]
+            else:
+                assert val1[1] == val2[1]
+        else:
+            assert val1 == val2
+
+
+def test_diff_opt():
+    b = BoolOption('b', '')
+    u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}])
+    #u.impl_add_consistency('not_equal', b)
+    s = SymLinkOption('s', u)
+    o = OptionDescription('o', '', [b, u, s])
+    o1 = OptionDescription('o1', '', [o])
+
+    a = dumps(o1)
+    q = loads(a)
+    _diff_opt(o1, q)
+    _diff_opt(o1.o, q.o)
+    _diff_opt(o1.o.b, q.o.b)
+    _diff_opt(o1.o.u, q.o.u)
+    _diff_opt(o1.o.s, q.o.s)
+
+
+def test_diff_opt_cache():
+    b = BoolOption('b', '')
+    u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}])
+    u.impl_add_consistency('not_equal', b)
+    s = SymLinkOption('s', u)
+    o = OptionDescription('o', '', [b, u, s])
+    o1 = OptionDescription('o1', '', [o])
+    o1.impl_build_cache()
+
+    a = dumps(o1)
+    q = loads(a)
+    _diff_opt(o1, q)
+    _diff_opt(o1.o, q.o)
+    _diff_opt(o1.o.b, q.o.b)
+    _diff_opt(o1.o.u, q.o.u)
+    _diff_opt(o1.o.s, q.o.s)
+
+
+def test_diff_opt_callback():
+    b = BoolOption('b', '', callback=return_value)
+    b2 = BoolOption('b2', '', callback=return_value, callback_params={'': ('yes',)})
+    b3 = BoolOption('b3', '', callback=return_value, callback_params={'': ('yes', (b, False)), 'value': ('no',)})
+    o = OptionDescription('o', '', [b, b2, b3])
+    o1 = OptionDescription('o1', '', [o])
+    o1.impl_build_cache()
+
+    a = dumps(o1)
+    q = loads(a)
+    _diff_opt(o1, q)
+    _diff_opt(o1.o, q.o)
+    _diff_opt(o1.o.b, q.o.b)
+    _diff_opt(o1.o.b2, q.o.b2)
+    _diff_opt(o1.o.b3, q.o.b3)
+
+
+def test_no_state_attr():
+    # all _state_xxx attributes should be deleted
+    b = BoolOption('b', '')
+    u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}])
+    s = SymLinkOption('s', u)
+    o = OptionDescription('o', '', [b, u, s])
+    o1 = OptionDescription('o1', '', [o])
+
+    a = dumps(o1)
+    q = loads(a)
+    _no_state(q)
+    _no_state(q.o)
+    _no_state(q.o.b)
+    _no_state(q.o.u)
+    _no_state(q.o.s)
+
+
+def test_state_config():
+    val1 = BoolOption('val1', "")
+    maconfig = OptionDescription('rootconfig', '', [val1])
+    try:
+        cfg = Config(maconfig, persistent=True, session_id='29090931')
+    except ValueError:
+        cfg = Config(maconfig, session_id='29090931')
+        cfg._impl_test = True
+    a = dumps(cfg)
+    q = loads(a)
+    _diff_opt(maconfig, q.cfgimpl_get_description())
+    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
+    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
+    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    try:
+        delete_session('29090931')
+    except ConfigError:
+        pass
+
+
+def test_state_properties():
+    val1 = BoolOption('val1', "")
+    maconfig = OptionDescription('rootconfig', '', [val1])
+    try:
+        cfg = Config(maconfig, persistent=True, session_id='29090932')
+    except ValueError:
+        cfg = Config(maconfig, session_id='29090932')
+        cfg._impl_test = True
+    cfg.read_write()
+    cfg.cfgimpl_get_settings()[val1].append('test')
+    a = dumps(cfg)
+    q = loads(a)
+    _diff_opt(maconfig, q.cfgimpl_get_description())
+    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
+    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
+    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    try:
+        delete_session('29090931')
+    except ConfigError:
+        pass
+
+
+def test_state_values():
+    val1 = BoolOption('val1', "")
+    maconfig = OptionDescription('rootconfig', '', [val1])
+    try:
+        cfg = Config(maconfig, persistent=True, session_id='29090933')
+    except ValueError:
+        cfg = Config(maconfig, session_id='29090933')
+        cfg._impl_test = True
+    cfg.val1 = True
+    a = dumps(cfg)
+    q = loads(a)
+    _diff_opt(maconfig, q.cfgimpl_get_description())
+    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
+    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
+    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    q.val1 = False
+    #assert cfg.val1 is True
+    assert q.val1 is False
+    try:
+        delete_session('29090931')
+    except ConfigError:
+        pass
+
+
+def test_state_values_owner():
+    val1 = BoolOption('val1', "")
+    maconfig = OptionDescription('rootconfig', '', [val1])
+    try:
+        cfg = Config(maconfig, persistent=True, session_id='29090934')
+    except ValueError:
+        cfg = Config(maconfig, session_id='29090934')
+        cfg._impl_test = True
+    owners.addowner('newowner')
+    cfg.cfgimpl_get_settings().setowner(owners.newowner)
+    cfg.val1 = True
+    a = dumps(cfg)
+    q = loads(a)
+    _diff_opt(maconfig, q.cfgimpl_get_description())
+    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
+    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
+    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    q.val1 = False
+    nval1 = q.cfgimpl_get_description().val1
+    assert q.getowner(nval1) == owners.newowner
+    try:
+        delete_session('29090931')
+    except ConfigError:
+        pass
index 6dbb721..0b2e3ac 100644 (file)
@@ -5,7 +5,7 @@ import autopath
 from tiramisu.config import Config
 from tiramisu.option import BoolOption, OptionDescription
 from tiramisu.setting import owners
-from tiramisu.setting import list_sessions, delete_session
+from tiramisu.storage import list_sessions, delete_session
 
 
 def test_non_persistent():
@@ -18,7 +18,7 @@ def test_list():
     b = BoolOption('b', '')
     o = OptionDescription('od', '', [b])
     c = Config(o, session_id='test_non_persistent')
-    from tiramisu.setting import list_sessions
+    c.cfgimpl_get_settings().remove('cache')
     assert 'test_non_persistent' in list_sessions()
     del(c)
     assert 'test_non_persistent' not in list_sessions()
@@ -43,7 +43,6 @@ def test_list_sessions_persistent():
         # storage is not persistent
         pass
     else:
-        from tiramisu.setting import list_sessions
         assert 'test_persistent' in list_sessions()
 
 
@@ -66,6 +65,7 @@ def test_create_persistent_retrieve():
     o = OptionDescription('od', '', [b])
     try:
         c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
     except ValueError:
         # storage is not persistent
         pass
@@ -75,10 +75,12 @@ def test_create_persistent_retrieve():
         assert c.b is True
         del(c)
         c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
         assert c.b is True
         assert 'test_persistent' in list_sessions()
         delete_session('test_persistent')
         c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
         assert c.b is None
         delete_session('test_persistent')
 
@@ -88,11 +90,13 @@ def test_two_persistent():
     o = OptionDescription('od', '', [b])
     try:
         c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
     except ValueError:
         # storage is not persistent
         pass
     else:
         c2 = Config(o, session_id='test_persistent', persistent=True)
+        c2.cfgimpl_get_settings().remove('cache')
         assert c.b is None
         assert c2.b is None
         c.b = False
@@ -109,11 +113,13 @@ def test_two_persistent_owner():
     o = OptionDescription('od', '', [b])
     try:
         c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
     except ValueError:
         # storage is not persistent
         pass
     else:
         c2 = Config(o, session_id='test_persistent', persistent=True)
+        c2.cfgimpl_get_settings().remove('cache')
         owners.addowner('persistent')
         assert c.getowner(b) == owners.default
         assert c2.getowner(b) == owners.default
@@ -124,3 +130,22 @@ def test_two_persistent_owner():
         assert c.getowner(b) == owners.persistent
         assert c2.getowner(b) == owners.persistent
         delete_session('test_persistent')
+
+
+def test_two_persistent_information():
+    b = BoolOption('b', '')
+    o = OptionDescription('od', '', [b])
+    try:
+        c = Config(o, session_id='test_persistent', persistent=True)
+        c.cfgimpl_get_settings().remove('cache')
+    except ValueError:
+        # storage is not persistent
+        pass
+    else:
+        c.impl_set_information('info', 'string')
+        assert c.impl_get_information('info') == 'string'
+        c2 = Config(o, session_id='test_persistent', persistent=True)
+        c2.cfgimpl_get_settings().remove('cache')
+        c2.cfgimpl_get_settings().remove('cache')
+        assert c2.impl_get_information('info') == 'string'
+        delete_session('test_persistent')
index 2cf41ca..de8a8c5 100644 (file)
@@ -23,11 +23,9 @@ from tiramisu.error import PropertiesOptionError, ConfigError
 from tiramisu.i18n import _
 # ____________________________________________________________
 
-def carry_out_calculation(name,
-                          config,
-                          callback,
-                          callback_params,
-                          index=None):
+
+def carry_out_calculation(name, config, callback, callback_params,
+                          index=None, max_len=None):
     """a function that carries out a calculation for an option's value
 
     :param name: the option name (`opt._name`)
@@ -40,36 +38,104 @@ def carry_out_calculation(name,
     :type callback_params: dict
     :param index: if an option is multi, only calculates the nth value
     :type index: int
+    :param max_len: max length for a multi
+    :type max_len: int
+
+    * if no callback_params:
+      => calculate()
+
+    * if callback_params={'': ('yes',)}
+      => calculate('yes')
+
+    * if callback_params={'value': ('yes',)}
+      => calculate(value='yes')
+
+    * if callback_params={'': ('yes', 'no')}
+      => calculate('yes', 'no')
+
+    * if callback_params={'value': ('yes', 'no')}
+      => ValueError()
+
+    * if callback_params={'': ((opt1, False),)}
+
+       - a simple option:
+         opt1 == 11
+         => calculate(11)
+
+       - a multi option:
+         opt1 == [1, 2, 3]
+         => calculate(1)
+         => calculate(2)
+         => calculate(3)
+
+    * if callback_params={'value': ((opt1, False),)}
+
+       - a simple option:
+         opt1 == 11
+         => calculate(value=11)
+
+       - a multi option:
+         opt1 == [1, 2, 3]
+         => calculate(value=1)
+         => calculate(value=2)
+         => calculate(value=3)
+
+    * if callback_params={'': ((opt1, False), (opt2, False))}
+
+      - a multi option with a simple option
+          opt1 == [1, 2, 3]
+          opt2 == 11
+          => calculate(1, 11)
+          => calculate(2, 11)
+          => calculate(3, 11)
+
+      - a multi option with an other multi option but with same length
+          opt1 == [1, 2, 3]
+          opt2 == [11, 12, 13]
+          callback_params={'': ((opt1, False), (opt2, False))}
+          => calculate(1, 11)
+          => calculate(2, 12)
+          => calculate(3, 13)
+
+      - a multi option with an other multi option but with different length
+          opt1 == [1, 2, 3]
+          opt2 == [11, 12]
+          callback_params={'': ((opt1, False), (opt2, False))}
+          => ConfigError()
+
+    * if callback_params={'value': ((opt1, False), (opt2, False))}
+      => ConfigError()
+
+    If index is not None, return a value, otherwise return:
+
+    * a list if one parameters have multi option
+    * a value otherwise
+
+    If calculate return list, this list is extend to return value.
     """
-    #callback, callback_params = option.getcallback()
-    #if callback_params is None:
-    #    callback_params = {}
     tcparams = {}
     one_is_multi = False
     len_multi = 0
 
-    for key, values in callback_params.items():
-        for value in values:
-            if type(value) == tuple:
-                path, check_disabled = value
-                if config is None:
-                    if check_disabled:
-                        continue
-                    raise ConfigError(_('no config specified but needed'))
+    for key, callbacks in callback_params.items():
+        for callbk in callbacks:
+            if isinstance(callbk, tuple):
+                option, force_permissive = callbk
+                # get value
                 try:
-                    opt_value = config._getattr(path, force_permissive=True)
-                    opt = config.unwrap_from_path(path, force_permissive=True)
+                    path = config.cfgimpl_get_description().impl_get_path_by_opt(option)
+                    value = config._getattr(path, force_permissive=True)
                 except PropertiesOptionError as err:
-                    if check_disabled:
+                    if force_permissive:
                         continue
                     raise ConfigError(_('unable to carry out a calculation, '
                                         'option {0} has properties: {1} for: '
-                                        '{2}').format(path, err.proptype,
+                                        '{2}').format(option._name, err.proptype,
                                                       name))
-                is_multi = opt.impl_is_multi()
+                is_multi = option.impl_is_multi()
                 if is_multi:
-                    if opt_value is not None:
-                        len_value = len(opt_value)
+                    if value is not None:
+                        len_value = len(value)
                         if len_multi != 0 and len_multi != len_value:
                             raise ConfigError(_('unable to carry out a '
                                               'calculation, option value with'
@@ -77,16 +143,23 @@ def carry_out_calculation(name,
                                               'length for: {0}').format(name))
                         len_multi = len_value
                     one_is_multi = True
-                tcparams.setdefault(key, []).append((opt_value, is_multi))
+                tcparams.setdefault(key, []).append((value, is_multi))
             else:
-                tcparams.setdefault(key, []).append((value, False))
+                tcparams.setdefault(key, []).append((callbk, False))
 
     if one_is_multi:
         ret = []
         if index:
-            range_ = [index]
+            if index < len_multi:
+                range_ = [index]
+            else:
+                range_ = []
+                ret = None
         else:
-            range_ = range(len_multi)
+            if max_len and max_len < len_multi:
+                range_ = range(max_len)
+            else:
+                range_ = range(len_multi)
         for incr in range_:
             tcp = {}
             params = []
@@ -97,15 +170,9 @@ def carry_out_calculation(name,
                         if key == '':
                             params.append(value[incr])
                         else:
-                            if len(value) > incr:
-                                tcp[key] = value[incr]
-                            else:
-                                tcp[key] = ''
+                            tcp[key] = value[incr]
                     else:
-                        if key == '':
-                            params.append(value)
-                        else:
-                            tcp[key] = value
+                        params.append(value)
             calc = calculate(name, callback, params, tcp)
             if index:
                 ret = calc
@@ -114,7 +181,6 @@ def carry_out_calculation(name,
                     ret.extend(calc)
                 else:
                     ret.append(calc)
-
         return ret
     else:
         tcp = {}
@@ -130,7 +196,6 @@ def carry_out_calculation(name,
 
 
 def calculate(name, callback, params, tcparams):
-    # FIXME we don't need the option's name down there.
     """wrapper that launches the 'callback'
 
     :param callback: callback name
index ae68e98..f1b2851 100644 (file)
 # ____________________________________________________________
 import weakref
 from tiramisu.error import PropertiesOptionError, ConfigError
-from tiramisu.option import OptionDescription, Option, SymLinkOption, \
-    BaseInformation
-from tiramisu.setting import groups, Settings, default_encoding, get_storage
-from tiramisu.value import Values
+from tiramisu.option import OptionDescription, Option, SymLinkOption
+from tiramisu.setting import groups, Settings, default_encoding
+from tiramisu.storage import get_storages, get_storage, set_storage, \
+    _impl_getstate_setting
+from tiramisu.value import Values, Multi
 from tiramisu.i18n import _
 
 
-class SubConfig(BaseInformation):
+class SubConfig(object):
     "sub configuration management entry"
     __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
 
@@ -252,10 +253,10 @@ class SubConfig(BaseInformation):
             :returns: list of matching Option objects
         """
         return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
-                                                first=False,
-                                                type_=type_,
-                                                _subpath=self.cfgimpl_get_path()
-                                                )
+                                                 first=False,
+                                                 type_=type_,
+                                                 _subpath=self.cfgimpl_get_path()
+                                                 )
 
     def find_first(self, bytype=None, byname=None, byvalue=None,
                    type_='option', display_error=True):
@@ -293,12 +294,13 @@ class SubConfig(BaseInformation):
                 return True
             try:
                 value = getattr(self, path)
-                if value == byvalue:
-                    return True
+                if isinstance(value, Multi):
+                    return byvalue in value
+                else:
+                    return value == byvalue
             except PropertiesOptionError:  # a property is a restriction
                                            # upon the access of the value
-                pass
-            return False
+                return False
 
         def _filter_by_type():
             if bytype is None:
@@ -323,15 +325,15 @@ class SubConfig(BaseInformation):
                 continue
             if not _filter_by_value():
                 continue
+            if not _filter_by_type():
+                continue
             #remove option with propertyerror, ...
-            if check_properties:
+            if byvalue is None and check_properties:
                 try:
                     value = getattr(self, path)
                 except PropertiesOptionError:
                     # a property restricts the access of the value
                     continue
-            if not _filter_by_type():
-                continue
             if type_ == 'value':
                 retval = value
             elif type_ == 'path':
@@ -501,11 +503,27 @@ class CommonConfig(SubConfig):
     def cfgimpl_get_meta(self):
         return self._impl_meta
 
+    # information
+    def impl_set_information(self, key, value):
+        """updates the information's attribute
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._impl_values.set_information(key, value)
+
+    def impl_get_information(self, key, default=None):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        return self._impl_values.get_information(key, default)
+
 
 # ____________________________________________________________
 class Config(CommonConfig):
     "main configuration management entry"
-    __slots__ = ('__weakref__', )
+    __slots__ = ('__weakref__', '_impl_test')
 
     def __init__(self, descr, session_id=None, persistent=False):
         """ Configuration option management master class
@@ -520,13 +538,49 @@ class Config(CommonConfig):
         :param persistent: if persistent, don't delete storage when leaving
         :type persistent: `boolean`
         """
-        storage = get_storage(self, session_id, persistent)
-        self._impl_settings = Settings(self, storage)
-        self._impl_values = Values(self, storage)
+        settings, values = get_storages(self, session_id, persistent)
+        self._impl_settings = Settings(self, settings)
+        self._impl_values = Values(self, values)
         super(Config, self).__init__(descr, weakref.ref(self))
         self._impl_build_all_paths()
         self._impl_meta = None
-        self._impl_informations = {}
+        #undocumented option used only in test script
+        self._impl_test = False
+
+    def __getstate__(self):
+        if self._impl_meta is not None:
+            raise ConfigError('cannot serialize Config with meta')
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        slots -= frozenset(['_impl_context', '__weakref__'])
+        state = {}
+        for slot in slots:
+            try:
+                state[slot] = getattr(self, slot)
+            except AttributeError:
+                pass
+        storage = self._impl_values._p_._storage
+        if not storage.serializable:
+            raise ConfigError('this storage is not serialisable, could be a '
+                              'none persistent storage')
+        state['_storage'] = {'session_id': storage.session_id,
+                             'persistent': storage.persistent}
+        state['_impl_setting'] = _impl_getstate_setting()
+        return state
+
+    def __setstate__(self, state):
+        for key, value in state.items():
+            if key not in ['_storage', '_impl_setting']:
+                setattr(self, key, value)
+        set_storage(**state['_impl_setting'])
+        self._impl_context = weakref.ref(self)
+        self._impl_settings.context = weakref.ref(self)
+        self._impl_values.context = weakref.ref(self)
+        storage = get_storage(test=self._impl_test, **state['_storage'])
+        self._impl_values._impl_setstate(storage)
+        self._impl_settings._impl_setstate(storage)
 
     def cfgimpl_reset_cache(self,
                             only_expired=False,
@@ -561,11 +615,10 @@ class Config(CommonConfig):
 #                child._impl_meta = self
 
 #        self._impl_children = children
-#        storage = get_storage(self, session_id, persistent)
-#        self._impl_settings = Settings(self, storage)
-#        self._impl_values = Values(self, storage)
+#        settings, values = get_storages(self, session_id, persistent)
+#        self._impl_settings = Settings(self, settings)
+#        self._impl_values = Values(self, values)
 #        self._impl_meta = None
-#        self._impl_informations = {}
 
 #    def cfgimpl_get_children(self):
 #        return self._impl_children
index 031fb50..10aba5c 100644 (file)
@@ -26,7 +26,7 @@ from copy import copy, deepcopy
 from types import FunctionType
 from IPy import IP
 
-from tiramisu.error import ConflictError
+from tiramisu.error import ConflictError, ConfigError
 from tiramisu.setting import groups, multitypes
 from tiramisu.i18n import _
 from tiramisu.autolib import carry_out_calculation
@@ -54,10 +54,78 @@ def valid_name(name):
 #
 
 
-class BaseInformation(object):
-    "interface for an option's information attribute"
-    __slots__ = ('_impl_informations',)
+class BaseOption(object):
+    """This abstract base class stands for attribute access
+    in options that have to be set only once, it is of course done in the
+    __setattr__ method
+    """
+    __slots__ = ('_name', '_requires', '_properties', '_readonly',
+                 '_consistencies', '_calc_properties', '_impl_informations',
+                 '_state_consistencies', '_state_readonly', '_state_requires',
+                 '_stated')
+
+    def __init__(self, name, doc, requires, properties):
+        if not valid_name(name):
+            raise ValueError(_("invalid name: {0} for option").format(name))
+        self._name = name
+        self._impl_informations = {}
+        self.impl_set_information('doc', doc)
+        self._calc_properties, self._requires = validate_requires_arg(
+            requires, self._name)
+        self._consistencies = None
+        if properties is None:
+            properties = tuple()
+        if not isinstance(properties, tuple):
+            raise TypeError(_('invalid properties type {0} for {1},'
+                            ' must be a tuple').format(
+                                type(properties),
+                                self._name))
+        if self._calc_properties is not None and properties is not tuple():
+            set_forbidden_properties = set(properties) & self._calc_properties
+            if set_forbidden_properties != frozenset():
+                raise ValueError('conflict: properties already set in '
+                                 'requirement {0}'.format(
+                                     list(set_forbidden_properties)))
+        self._properties = properties  # 'hidden', 'disabled'...
 
+    def __setattr__(self, name, value):
+        """set once and only once some attributes in the option,
+        like `_name`. `_name` cannot be changed one the option and
+        pushed in the :class:`tiramisu.option.OptionDescription`.
+
+        if the attribute `_readonly` is set to `True`, the option is
+        "frozen" (which has noting to do with the high level "freeze"
+        propertie or "read_only" property)
+        """
+        if not name.startswith('_state') and name not in ('_cache_paths',
+                                                          '_consistencies'):
+            is_readonly = False
+            # never change _name
+            if name == '_name':
+                try:
+                    self._name
+                    #so _name is already set
+                    is_readonly = True
+                except:
+                    pass
+            try:
+                if self._readonly is True:
+                    if value is True:
+                        # already readonly and try to re set readonly
+                        # don't raise, just exit
+                        return
+                    is_readonly = True
+            except AttributeError:
+                pass
+            if is_readonly:
+                raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
+                                       " read-only").format(
+                                           self.__class__.__name__,
+                                           self._name,
+                                           name))
+        object.__setattr__(self, name, value)
+
+    # information
     def impl_set_information(self, key, value):
         """updates the information's attribute
         (which is a dictionary)
@@ -65,47 +133,206 @@ class BaseInformation(object):
         :param key: information's key (ex: "help", "doc"
         :param value: information's value (ex: "the help string")
         """
-        try:
-            self._impl_informations[key] = value
-        except AttributeError:
-            raise AttributeError(_('{0} has no attribute '
-                                   'impl_set_information').format(
-                                       self.__class__.__name__))
+        self._impl_informations[key] = value
 
     def impl_get_information(self, key, default=None):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
-        try:
-            if key in self._impl_informations:
-                return self._impl_informations[key]
-            elif default is not None:
-                return default
+        if key in self._impl_informations:
+            return self._impl_informations[key]
+        elif default is not None:
+            return default
+        else:
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
+    # serialize/unserialize
+    def _impl_convert_consistencies(self, descr, load=False):
+        """during serialization process, many things have to be done.
+        one of them is the localisation of the options.
+        The paths are set once for all.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._consistencies is None:
+            self._state_consistencies = None
+        elif load and self._state_consistencies is None:
+            self._consistencies = None
+            del(self._state_consistencies)
+        else:
+            if load:
+                consistencies = self._state_consistencies
+            else:
+                consistencies = self._consistencies
+            if isinstance(consistencies, list):
+                new_value = []
+                for consistency in consistencies:
+                    if load:
+                        new_value.append((consistency[0],
+                                          descr.impl_get_opt_by_path(
+                                              consistency[1])))
+                    else:
+                        new_value.append((consistency[0],
+                                          descr.impl_get_path_by_opt(
+                                              consistency[1])))
+
+            else:
+                new_value = {}
+                for key, _consistencies in consistencies.items():
+                    new_value[key] = []
+                    for key_cons, _cons in _consistencies:
+                        _list_cons = []
+                        for _con in _cons:
+                            if load:
+                                _list_cons.append(
+                                    descr.impl_get_opt_by_path(_con))
+                            else:
+                                _list_cons.append(
+                                    descr.impl_get_path_by_opt(_con))
+                        new_value[key].append((key_cons, tuple(_list_cons)))
+            if load:
+                del(self._state_consistencies)
+                self._consistencies = new_value
+            else:
+                self._state_consistencies = new_value
+
+    def _impl_convert_requires(self, descr, load=False):
+        """export of the requires during the serialization process
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._requires is None:
+            self._state_requires = None
+        elif load and self._state_requires is None:
+            self._requires = None
+            del(self._state_requires)
+        else:
+            if load:
+                _requires = self._state_requires
             else:
-                raise ValueError(_("Information's item"
-                                   "not found: {0}").format(key))
+                _requires = self._requires
+            new_value = []
+            for requires in _requires:
+                new_requires = []
+                for require in requires:
+                    if load:
+                        new_require = [descr.impl_get_opt_by_path(require[0])]
+                    else:
+                        new_require = [descr.impl_get_path_by_opt(require[0])]
+                    new_require.extend(require[1:])
+                    new_requires.append(tuple(new_require))
+                new_value.append(tuple(new_requires))
+            if load:
+                del(self._state_requires)
+                self._requires = new_value
+            else:
+                self._state_requires = new_value
+
+    # serialize
+    def _impl_getstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :param descr: the parent :class:`tiramisu.option.OptionDescription`
+        """
+        self._stated = True
+        for func in dir(self):
+            if func.startswith('_impl_convert_'):
+                getattr(self, func)(descr)
+        try:
+            self._state_readonly = self._readonly
+        except AttributeError:
+            pass
+
+    def __getstate__(self, stated=True):
+        """special method to enable the serialization with pickle
+        Usualy, a `__getstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter stated: if stated is `True`, the serialization protocol
+                           can be performed, not ready yet otherwise
+        :parameter type: bool
+        """
+        try:
+            self._stated
         except AttributeError:
-            raise AttributeError(_('{0} has no attribute '
-                                   'impl_get_information').format(
-                                       self.__class__.__name__))
+            raise SystemError(_('cannot serialize Option, '
+                                'only in OptionDescription'))
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        slots -= frozenset(['_cache_paths', '__weakref__'])
+        states = {}
+        for slot in slots:
+            # remove variable if save variable converted
+            # in _state_xxxx variable
+            if '_state' + slot not in slots:
+                if slot.startswith('_state'):
+                    # should exists
+                    states[slot] = getattr(self, slot)
+                    # remove _state_xxx variable
+                    self.__delattr__(slot)
+                else:
+                    try:
+                        states[slot] = getattr(self, slot)
+                    except AttributeError:
+                        pass
+        if not stated:
+            del(states['_stated'])
+        return states
+
+    # unserialize
+    def _impl_setstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        """
+        for func in dir(self):
+            if func.startswith('_impl_convert_'):
+                getattr(self, func)(descr, load=True)
+        try:
+            self._readonly = self._state_readonly
+            del(self._state_readonly)
+            del(self._stated)
+        except AttributeError:
+            pass
 
+    def __setstate__(self, state):
+        """special method that enables us to serialize (pickle)
+
+        Usualy, a `__setstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter state: a dict is passed to the loads, it is the attributes
+                          of the options object
+        :type state: dict
+        """
+        for key, value in state.items():
+            setattr(self, key, value)
 
-class Option(BaseInformation):
+
+class Option(BaseOption):
     """
     Abstract base class for configuration option's.
 
-    Reminder: an Option object is **not** a container for the value
+    Reminder: an Option object is **not** a container for the value.
     """
-    __slots__ = ('_name', '_requires', '_multi', '_validator',
-                 '_default_multi', '_default', '_properties', '_callback',
-                 '_multitype', '_master_slaves', '_consistencies',
-                 '_calc_properties', '__weakref__')
+    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
+                 '_state_callback', '_callback', '_multitype',
+                 '_master_slaves', '__weakref__')
     _empty = ''
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None):
         """
         :param name: the option's name
@@ -120,26 +347,17 @@ class Option(BaseInformation):
         :param callback: the name of a function. If set, the function's output
                          is responsible of the option's value
         :param callback_params: the callback's parameter
-        :param validator: the name of a function wich stands for a custom
+        :param validator: the name of a function which stands for a custom
                           validation of the value
-        :param validator_args: the validator's parameters
+        :param validator_params: the validator's parameters
+        :param properties: tuple of default properties
 
         """
-        if not valid_name(name):
-            raise ValueError(_("invalid name: {0} for option").format(name))
-        self._name = name
-        self._impl_informations = {}
-        self.impl_set_information('doc', doc)
-        self._calc_properties, self._requires = validate_requires_arg(
-            requires, self._name)
+        super(Option, self).__init__(name, doc, requires, properties)
         self._multi = multi
-        self._consistencies = None
         if validator is not None:
-            if type(validator) != FunctionType:
-                raise TypeError(_("validator must be a function"))
-            if validator_args is None:
-                validator_args = {}
-            self._validator = (validator, validator_args)
+            validate_callback(validator, validator_params, 'validator')
+            self._validator = (validator, validator_params)
         else:
             self._validator = None
         if not self._multi and default_multi is not None:
@@ -161,11 +379,7 @@ class Option(BaseInformation):
                              "no callback defined"
                              " yet for option {0}").format(name))
         if callback is not None:
-            if type(callback) != FunctionType:
-                raise ValueError('callback must be a function')
-            if callback_params is not None and \
-                    not isinstance(callback_params, dict):
-                raise ValueError('callback_params must be a dict')
+            validate_callback(callback, callback_params, 'callback')
             self._callback = (callback, callback_params)
         else:
             self._callback = None
@@ -176,14 +390,6 @@ class Option(BaseInformation):
             self._default_multi = default_multi
         self.impl_validate(default)
         self._default = default
-        if properties is None:
-            properties = tuple()
-        if not isinstance(properties, tuple):
-            raise TypeError(_('invalid properties type {0} for {1},'
-                            ' must be a tuple').format(
-                                type(properties),
-                                self._name))
-        self._properties = properties  # 'hidden', 'disabled'...
 
     def _launch_consistency(self, func, opt, vals, context, index, opt_):
         if context is not None:
@@ -240,11 +446,23 @@ class Option(BaseInformation):
 
         def val_validator(val):
             if self._validator is not None:
-                callback_params = deepcopy(self._validator[1])
-                callback_params.setdefault('', []).insert(0, val)
-                return carry_out_calculation(self._name, config=context,
-                                             callback=self._validator[0],
-                                             callback_params=callback_params)
+                if self._validator[1] is not None:
+                    validator_params = deepcopy(self._validator[1])
+                    if '' in validator_params:
+                        lst = list(validator_params[''])
+                        lst.insert(0, val)
+                        validator_params[''] = tuple(lst)
+                    else:
+                        validator_params[''] = (val,)
+                else:
+                    validator_params = {'': (val,)}
+                ret = carry_out_calculation(self._name, config=context,
+                                            callback=self._validator[0],
+                                            callback_params=validator_params)
+                if ret not in [False, True]:
+                    raise ConfigError(_('validator should return a boolean, '
+                                        'not {0}').format(ret))
+                return ret
             else:
                 return True
 
@@ -345,6 +563,40 @@ class Option(BaseInformation):
                                "must be different as {2} option"
                                "").format(value, self._name, optname))
 
+    def _impl_convert_callbacks(self, descr, load=False):
+        if not load and self._callback is None:
+            self._state_callback = None
+        elif load and self._state_callback is None:
+            self._callback = None
+            del(self._state_callback)
+        else:
+            if load:
+                callback, callback_params = self._state_callback
+            else:
+                callback, callback_params = self._callback
+            if callback_params is not None:
+                cllbck_prms = {}
+                for key, values in callback_params.items():
+                    vls = []
+                    for value in values:
+                        if isinstance(value, tuple):
+                            if load:
+                                value = (descr.impl_get_opt_by_path(value[0]),
+                                         value[1])
+                            else:
+                                value = (descr.impl_get_path_by_opt(value[0]),
+                                         value[1])
+                        vls.append(value)
+                    cllbck_prms[key] = tuple(vls)
+            else:
+                cllbck_prms = None
+
+            if load:
+                del(self._state_callback)
+                self._callback = (callback, cllbck_prms)
+            else:
+                self._state_callback = (callback, cllbck_prms)
+
 
 class ChoiceOption(Option):
     """represents a choice out of several objects.
@@ -358,7 +610,7 @@ class ChoiceOption(Option):
     def __init__(self, name, doc, values, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
                  callback_params=None, open_values=False, validator=None,
-                 validator_args=None, properties=()):
+                 validator_params=None, properties=()):
         """
         :param values: is a list of values the option can possibly take
         """
@@ -376,7 +628,7 @@ class ChoiceOption(Option):
                                            requires=requires,
                                            multi=multi,
                                            validator=validator,
-                                           validator_args=validator_args,
+                                           validator_params=validator_params,
                                            properties=properties)
 
     def impl_get_values(self):
@@ -450,10 +702,11 @@ else:
                 raise ValueError(_('value must be an unicode'))
 
 
-class SymLinkOption(object):
-    __slots__ = ('_name', '_opt')
+class SymLinkOption(BaseOption):
+    __slots__ = ('_name', '_opt', '_state_opt')
     _opt_type = 'symlink'
-    _consistencies = None
+    #not return _opt consistencies
+    _consistencies = {}
 
     def __init__(self, name, opt):
         self._name = name
@@ -462,24 +715,41 @@ class SymLinkOption(object):
                                'must be an option '
                                'for symlink {0}').format(name))
         self._opt = opt
+        self._readonly = True
 
     def __getattr__(self, name):
-        if name in ('_name', '_opt', '_consistencies'):
+        if name in ('_name', '_opt', '_opt_type', '_readonly'):
             return object.__getattr__(self, name)
         else:
             return getattr(self._opt, name)
 
+    def _impl_getstate(self, descr):
+        super(SymLinkOption, self)._impl_getstate(descr)
+        self._state_opt = descr.impl_get_path_by_opt(self._opt)
+
+    def _impl_setstate(self, descr):
+        self._opt = descr.impl_get_opt_by_path(self._state_opt)
+        del(self._state_opt)
+        super(SymLinkOption, self)._impl_setstate(descr)
+
+    def _impl_convert_consistencies(self, descr, load=False):
+        if load:
+            del(self._state_consistencies)
+        else:
+            self._state_consistencies = None
+
 
 class IPOption(Option):
     "represents the choice of an ip"
-    __slots__ = ('_only_private',)
+    __slots__ = ('_only_private', '_allow_reserved')
     _opt_type = 'ip'
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
-                 properties=None, only_private=False):
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, only_private=False, allow_reserved=False):
         self._only_private = only_private
+        self._allow_reserved = allow_reserved
         super(IPOption, self).__init__(name, doc, default=default,
                                        default_multi=default_multi,
                                        callback=callback,
@@ -487,12 +757,12 @@ class IPOption(Option):
                                        requires=requires,
                                        multi=multi,
                                        validator=validator,
-                                       validator_args=validator_args,
+                                       validator_params=validator_params,
                                        properties=properties)
 
     def _validate(self, value):
         ip = IP('{0}/32'.format(value))
-        if ip.iptype() == 'RESERVED':
+        if not self._allow_reserved and ip.iptype() == 'RESERVED':
             raise ValueError(_("IP mustn't not be in reserved class"))
         if self._only_private and not ip.iptype() == 'PRIVATE':
             raise ValueError(_("IP must be in private class"))
@@ -513,7 +783,7 @@ class PortOption(Option):
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None, allow_range=False, allow_zero=False,
                  allow_wellknown=True, allow_registred=True,
                  allow_private=False):
@@ -547,7 +817,7 @@ class PortOption(Option):
                                          requires=requires,
                                          multi=multi,
                                          validator=validator,
-                                         validator_args=validator_args,
+                                         validator_params=validator_params,
                                          properties=properties)
 
     def _validate(self, value):
@@ -575,7 +845,7 @@ class NetworkOption(Option):
     def _validate(self, value):
         ip = IP(value)
         if ip.iptype() == 'RESERVED':
-            raise ValueError(_("network mustn't not be in reserved class"))
+            raise ValueError(_("network shall not be in reserved class"))
 
 
 class NetmaskOption(Option):
@@ -632,7 +902,7 @@ class DomainnameOption(Option):
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None, allow_ip=False, type_='domainname'):
         #netbios: for MS domain
         #hostname: to identify the device
@@ -651,7 +921,7 @@ class DomainnameOption(Option):
                                                requires=requires,
                                                multi=multi,
                                                validator=validator,
-                                               validator_args=validator_args,
+                                               validator_params=validator_params,
                                                properties=properties)
 
     def _validate(self, value):
@@ -674,7 +944,7 @@ class DomainnameOption(Option):
                 raise ValueError(_("invalid value for {0}, must have dot"
                                    "").format(self._name))
         if len(value) > length:
-            raise ValueError(_("invalid domainname's length for "
+            raise ValueError(_("invalid domainname's length for"
                                " {0} (max {1})").format(self._name, length))
         if len(value) == 1:
             raise ValueError(_("invalid domainname's length for {0} (min 2)"
@@ -684,25 +954,23 @@ class DomainnameOption(Option):
             raise ValueError(_('invalid domainname'))
 
 
-class OptionDescription(BaseInformation):
+class OptionDescription(BaseOption):
     """Config's schema (organisation, group) and container of Options
     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
     """
     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
-                 '_properties', '_children', '_consistencies',
-                 '_calc_properties', '__weakref__')
+                 '_state_group_type', '_properties', '_children',
+                 '_consistencies', '_calc_properties', '__weakref__',
+                 '_readonly', '_impl_informations', '_state_requires',
+                 '_state_consistencies', '_stated', '_state_readonly')
+    _opt_type = 'optiondescription'
 
     def __init__(self, name, doc, children, requires=None, properties=None):
         """
         :param children: a list of options (including optiondescriptions)
 
         """
-        if not valid_name(name):
-            raise ValueError(_("invalid name: "
-                               " {0} for optiondescription").format(name))
-        self._name = name
-        self._impl_informations = {}
-        self.impl_set_information('doc', doc)
+        super(OptionDescription, self).__init__(name, doc, requires, properties)
         child_names = [child._name for child in children]
         #better performance like this
         valid_child = copy(child_names)
@@ -714,16 +982,7 @@ class OptionDescription(BaseInformation):
                                       '{0}').format(child))
             old = child
         self._children = (tuple(child_names), tuple(children))
-        self._calc_properties, self._requires = validate_requires_arg(requires, self._name)
         self._cache_paths = None
-        self._consistencies = None
-        if properties is None:
-            properties = tuple()
-        if not isinstance(properties, tuple):
-            raise TypeError(_('invalid properties type {0} for {1},'
-                              ' must be a tuple').format(type(properties),
-                                                         self._name))
-        self._properties = properties  # 'hidden', 'disabled'...
         # the group_type is useful for filtering OptionDescriptions in a config
         self._group_type = groups.default
 
@@ -731,6 +990,8 @@ class OptionDescription(BaseInformation):
         return self.impl_get_information('doc')
 
     def __getattr__(self, name):
+        if name in self.__slots__:
+            return object.__getattribute__(self, name)
         try:
             return self._children[1][self._children[0].index(name)]
         except ValueError:
@@ -767,13 +1028,16 @@ class OptionDescription(BaseInformation):
                          cache_path=None,
                          cache_option=None,
                          _currpath=None,
-                         _consistencies=None):
+                         _consistencies=None,
+                         force_no_consistencies=False):
         if _currpath is None and self._cache_paths is not None:
+            # cache already set
             return
         if _currpath is None:
             save = True
             _currpath = []
-            _consistencies = {}
+            if not force_no_consistencies:
+                _consistencies = {}
         else:
             save = False
         if cache_path is None:
@@ -781,15 +1045,16 @@ class OptionDescription(BaseInformation):
             cache_option = []
         for option in self.impl_getchildren():
             attr = option._name
-            if attr.startswith('_cfgimpl'):
-                continue
             if option in cache_option:
                 raise ConflictError(_('duplicate option: {0}').format(option))
 
             cache_option.append(option)
+            if not force_no_consistencies:
+                option._readonly = True
             cache_path.append(str('.'.join(_currpath + [attr])))
             if not isinstance(option, OptionDescription):
-                if option._consistencies is not None:
+                if not force_no_consistencies and \
+                        option._consistencies is not None:
                     for consistency in option._consistencies:
                         func, opt = consistency
                         opts = (option, opt)
@@ -802,11 +1067,14 @@ class OptionDescription(BaseInformation):
                 option.impl_build_cache(cache_path,
                                         cache_option,
                                         _currpath,
-                                        _consistencies)
+                                        _consistencies,
+                                        force_no_consistencies)
                 _currpath.pop()
         if save:
             self._cache_paths = (tuple(cache_option), tuple(cache_path))
-            self._consistencies = _consistencies
+            if not force_no_consistencies:
+                self._consistencies = _consistencies
+                self._readonly = True
 
     def impl_get_opt_by_path(self, path):
         try:
@@ -870,7 +1138,7 @@ class OptionDescription(BaseInformation):
                     raise ValueError(_("no child has same nom has master group"
                                        " for: {0}").format(self._name))
         else:
-            raise ValueError(_('group_type : {0}'
+            raise ValueError(_('group_type: {0}'
                                ' not allowed').format(group_type))
 
     def impl_get_group_type(self):
@@ -891,6 +1159,56 @@ class OptionDescription(BaseInformation):
                     return False
         return True
 
+    def _impl_getstate(self, descr=None):
+        """enables us to export into a dict
+        :param descr: parent :class:`tiramisu.option.OptionDescription`
+        """
+        if descr is None:
+            self.impl_build_cache()
+            descr = self
+        super(OptionDescription, self)._impl_getstate(descr)
+        self._state_group_type = str(self._group_type)
+        for option in self.impl_getchildren():
+            option._impl_getstate(descr)
+
+    def __getstate__(self):
+        """special method to enable the serialization with pickle
+        """
+        stated = True
+        try:
+            # the `_state` attribute is a flag that which tells us if
+            # the serialization can be performed
+            self._stated
+        except AttributeError:
+            # if cannot delete, _impl_getstate never launch
+            # launch it recursivement
+            # _stated prevent __getstate__ launch more than one time
+            # _stated is delete, if re-serialize, re-lauch _impl_getstate
+            self._impl_getstate()
+            stated = False
+        return super(OptionDescription, self).__getstate__(stated)
+
+    def _impl_setstate(self, descr=None):
+        """enables us to import from a dict
+        :param descr: parent :class:`tiramisu.option.OptionDescription`
+        """
+        if descr is None:
+            self._cache_paths = None
+            self.impl_build_cache(force_no_consistencies=True)
+            descr = self
+        self._group_type = getattr(groups, self._state_group_type)
+        del(self._state_group_type)
+        super(OptionDescription, self)._impl_setstate(descr)
+        for option in self.impl_getchildren():
+            option._impl_setstate(descr)
+
+    def __setstate__(self, state):
+        super(OptionDescription, self).__setstate__(state)
+        try:
+            self._stated
+        except AttributeError:
+            self._impl_setstate()
+
 
 def validate_requires_arg(requires, name):
     """check malformed requirements
@@ -906,6 +1224,8 @@ def validate_requires_arg(requires, name):
     ret_requires = {}
     config_action = {}
 
+    # start parsing all requires given by user (has dict)
+    # transforme it to a tuple
     for require in requires:
         if not type(require) == dict:
             raise ValueError(_("malformed requirements type for option:"
@@ -919,6 +1239,7 @@ def validate_requires_arg(requires, name):
                              '{2}'.format(name,
                                           unknown_keys,
                                           valid_keys))
+        # prepare all attributes
         try:
             option = require['option']
             expected = require['expected']
@@ -967,12 +1288,43 @@ def validate_requires_arg(requires, name):
                                             inverse, transitive, same_action)
         else:
             ret_requires[action][option][1].append(expected)
+    # transform dict to tuple
     ret = []
     for opt_requires in ret_requires.values():
         ret_action = []
         for require in opt_requires.values():
-            req = (require[0], tuple(require[1]), require[2], require[3],
-                   require[4], require[5])
-            ret_action.append(req)
+            ret_action.append((require[0], tuple(require[1]), require[2],
+                               require[3], require[4], require[5]))
         ret.append(tuple(ret_action))
     return frozenset(config_action.keys()), tuple(ret)
+
+
+def validate_callback(callback, callback_params, type_):
+    if type(callback) != FunctionType:
+        raise ValueError(_('{0} should be a function').format(type_))
+    if callback_params is not None:
+        if not isinstance(callback_params, dict):
+            raise ValueError(_('{0}_params should be a dict').format(type_))
+        for key, callbacks in callback_params.items():
+            if key != '' and len(callbacks) != 1:
+                raise ValueError(_('{0}_params with key {1} should not have '
+                                   'length different to 1').format(type_,
+                                                                   key))
+            if not isinstance(callbacks, tuple):
+                raise ValueError(_('{0}_params should be tuple for key "{1}"'
+                                   ).format(type_, key))
+            for callbk in callbacks:
+                if isinstance(callbk, tuple):
+                    option, force_permissive = callbk
+                    if type_ == 'validator' and not force_permissive:
+                        raise ValueError(_('validator not support tuple'))
+                    if not isinstance(option, Option) and not \
+                            isinstance(option, SymLinkOption):
+                        raise ValueError(_('{0}_params should have an option '
+                                           'not a {0} for first argument'
+                                           ).format(type_, type(option)))
+                    if force_permissive not in [True, False]:
+                        raise ValueError(_('{0}_params should have a boolean'
+                                           'not a {0} for second argument'
+                                           ).format(type_, type(
+                                               force_permissive)))
index 879656c..684ec74 100644 (file)
@@ -24,43 +24,92 @@ from time import time
 from copy import copy
 import weakref
 from tiramisu.error import (RequirementError, PropertiesOptionError,
-                            ConstError, ConfigError)
+                            ConstError)
 from tiramisu.i18n import _
 
 
+"Default encoding for display a Config if raise UnicodeEncodeError"
 default_encoding = 'utf-8'
+
+"""If cache and expire is enable, time before cache is expired.
+This delay start first time value/setting is set in cache, even if
+user access several time to value/setting
+"""
 expires_time = 5
-ro_remove = ('permissive', 'hidden')
-ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen',
-             'mandatory')
-rw_remove = ('permissive', 'everything_frozen', 'mandatory')
-rw_append = ('frozen', 'disabled', 'validator', 'hidden')
-default_properties = ('expire', 'validator')
+"""List of default properties (you can add new one if needed).
+
+For common properties and personalise properties, if a propery is set for
+an Option and for the Config together, Setting raise a PropertiesOptionError
+
+* Common properties:
+
+hidden
+    option with this property can only get value in read only mode. This
+    option is not available in read write mode.
+
+disabled
+    option with this property cannot be set/get
+
+frozen
+    cannot set value for option with this properties if 'frozen' is set in
+    config
+
+mandatory
+    should set value for option with this properties if 'mandatory' is set in
+    config
 
 
-class StorageType:
-    default_storage = 'dictionary'
-    storage_type = None
+* Special property:
 
-    def set_storage(self, name):
-        if self.storage_type is not None:
-            raise ConfigError(_('storage_type is already set, cannot rebind it'))
-        self.storage_type = name
+permissive
+    option with 'permissive' cannot raise PropertiesOptionError for properties
+    set in permissive
+    config with 'permissive', whole option in this config cannot raise
+    PropertiesOptionError for properties set in permissive
 
-    def get_storage(self):
-        if self.storage_type is None:
-            self.storage_type = self.default_storage
-        storage = self.storage_type
-        return 'tiramisu.storage.{0}.storage'.format(
-            storage)
+* Special Config properties:
 
+cache
+    if set, enable cache settings and values
 
-storage_type = StorageType()
+expire
+    if set, settings and values in cache expire after ``expires_time``
 
+everything_frozen
+    whole option in config are frozen (even if option have not frozen
+    property)
 
-class _NameSpace:
+validator
+    launch validator set by user in option (this property has no effect
+    for internal validator)
+"""
+default_properties = ('cache', 'expire', 'validator')
+
+"""Config can be in two defaut mode:
+
+read_only
+    you can get all variables not disabled but you cannot set any variables
+    if a value has a callback without any value, callback is launch and value
+    of this variable can change
+    you cannot access to mandatory variable without values
+
+read_write
+    you can get all variables not disabled and not hidden
+    you can set all variables not frozen
+"""
+ro_append = set(['frozen', 'disabled', 'validator', 'everything_frozen',
+                'mandatory'])
+ro_remove = set(['permissive', 'hidden'])
+rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
+rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
+
+
+# ____________________________________________________________
+class _NameSpace(object):
     """convenient class that emulates a module
-    and builds constants (that is, unique names)"""
+    and builds constants (that is, unique names)
+    when attribute is added, we cannot delete it
+    """
 
     def __setattr__(self, name, value):
         if name in self.__dict__:
@@ -73,7 +122,6 @@ class _NameSpace:
         raise ValueError(name)
 
 
-# ____________________________________________________________
 class GroupModule(_NameSpace):
     "emulates a module to manage unique group (OptionDescription) names"
     class GroupType(str):
@@ -91,21 +139,8 @@ class GroupModule(_NameSpace):
         *master* means : groups that have the 'master' attribute set
         """
         pass
-# setting.groups (emulates a module)
-groups = GroupModule()
 
 
-def populate_groups():
-    "populates the available groups in the appropriate namespaces"
-    groups.master = groups.MasterGroupType('master')
-    groups.default = groups.DefaultGroupType('default')
-    groups.family = groups.GroupType('family')
-
-# names are in the module now
-populate_groups()
-
-
-# ____________________________________________________________
 class OwnerModule(_NameSpace):
     """emulates a module to manage unique owner names.
 
@@ -119,15 +154,52 @@ class OwnerModule(_NameSpace):
     class DefaultOwner(Owner):
         """groups that are default (typically 'default')"""
         pass
-# setting.owners (emulates a module)
-owners = OwnerModule()
+
+
+class MultiTypeModule(_NameSpace):
+    "namespace for the master/slaves"
+    class MultiType(str):
+        pass
+
+    class DefaultMultiType(MultiType):
+        pass
+
+    class MasterMultiType(MultiType):
+        pass
+
+    class SlaveMultiType(MultiType):
+        pass
+
+
+# ____________________________________________________________
+def populate_groups():
+    """populates the available groups in the appropriate namespaces
+
+    groups.default
+        default group set when creating a new optiondescription
+
+    groups.master
+        master group is a special optiondescription, all suboptions should be
+        multi option and all values should have same length, to find master's
+        option, the optiondescription's name should be same than de master's
+        option
+
+    groups.family
+        example of group, no special behavior with this group's type
+    """
+    groups.default = groups.DefaultGroupType('default')
+    groups.master = groups.MasterGroupType('master')
+    groups.family = groups.GroupType('family')
 
 
 def populate_owners():
     """populates the available owners in the appropriate namespaces
 
-    - 'user' is the generic is the generic owner.
-    - 'default' is the config owner after init time
+    default
+        is the config owner after init time
+
+    user
+        is the generic is the generic owner
     """
     setattr(owners, 'default', owners.DefaultOwner('default'))
     setattr(owners, 'user', owners.Owner('user'))
@@ -139,36 +211,38 @@ def populate_owners():
         setattr(owners, name, owners.Owner(name))
     setattr(owners, 'addowner', addowner)
 
-# names are in the module now
-populate_owners()
-
 
-class MultiTypeModule(_NameSpace):
-    "namespace for the master/slaves"
-    class MultiType(str):
-        pass
-
-    class DefaultMultiType(MultiType):
-        pass
-
-    class MasterMultiType(MultiType):
-        pass
+def populate_multitypes():
+    """all multi option should have a type, this type is automaticly set do
+    not touch this
 
-    class SlaveMultiType(MultiType):
-        pass
+    default
+        default's multi option set if not master or slave
 
-multitypes = MultiTypeModule()
+    master
+        master's option in a group with master's type, name of this option
+        should be the same name of the optiondescription
 
+    slave
+        slave's option in a group with master's type
 
-def populate_multitypes():
-    "populates the master/slave namespace"
+    """
     setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
     setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
     setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
 
+
+# ____________________________________________________________
+# populate groups, owners and multitypes with default attributes
+groups = GroupModule()
+populate_groups()
+owners = OwnerModule()
+populate_owners()
+multitypes = MultiTypeModule()
 populate_multitypes()
 
 
+# ____________________________________________________________
 class Property(object):
     "a property is responsible of the option's value access rules"
     __slots__ = ('_setting', '_properties', '_opt', '_path')
@@ -191,7 +265,8 @@ class Property(object):
     def remove(self, propname):
         if propname in self._properties:
             self._properties.remove(propname)
-            self._setting._setproperties(self._properties, self._opt, self._path)
+            self._setting._setproperties(self._properties, self._opt,
+                                         self._path)
 
     def reset(self):
         self._setting.reset(_path=self._path)
@@ -203,39 +278,6 @@ class Property(object):
         return str(list(self._properties))
 
 
-def set_storage(name, **args):
-    storage_type.set_storage(name)
-    settings = __import__(storage_type.get_storage(), globals(), locals(),
-                          ['Setting']).Setting()
-    for option, value in args.items():
-        try:
-            getattr(settings, option)
-            setattr(settings, option, value)
-        except AttributeError:
-            raise ValueError(_('option {0} not already exists in storage {1}'
-                               '').format(option, name))
-
-
-def get_storage(context, session_id, persistent):
-    def gen_id(config):
-        return str(id(config)) + str(time())
-
-    if session_id is None:
-        session_id = gen_id(context)
-    return __import__(storage_type.get_storage(), globals(), locals(),
-                      ['Storage']).Storage(session_id, persistent)
-
-
-def list_sessions():
-    return __import__(storage_type.get_storage(), globals(), locals(),
-                      ['list_sessions']).list_sessions()
-
-
-def delete_session(session_id):
-    return __import__(storage_type.get_storage(), globals(), locals(),
-                      ['delete_session']).delete_session(session_id)
-
-
 #____________________________________________________________
 class Settings(object):
     "``Config()``'s configuration options"
@@ -254,9 +296,7 @@ class Settings(object):
         # generic owner
         self._owner = owners.user
         self.context = weakref.ref(context)
-        import_lib = 'tiramisu.storage.{0}.setting'.format(storage.storage)
-        self._p_ = __import__(import_lib, globals(), locals(), ['Settings']
-                              ).Settings(storage)
+        self._p_ = storage
 
     #____________________________________________________________
     # properties methods
@@ -268,7 +308,7 @@ class Settings(object):
         return str(list(self._getproperties()))
 
     def __getitem__(self, opt):
-        path = self._get_opt_path(opt)
+        path = self._get_path_by_opt(opt)
         return self._getitem(opt, path)
 
     def _getitem(self, opt, path):
@@ -285,7 +325,7 @@ class Settings(object):
             self._p_.reset_all_propertives()
         else:
             if opt is not None and _path is None:
-                _path = self._get_opt_path(opt)
+                _path = self._get_path_by_opt(opt)
             self._p_.reset_properties(_path)
         self.context().cfgimpl_reset_cache()
 
@@ -297,18 +337,21 @@ class Settings(object):
                 raise ValueError(_('if opt is not None, path should not be'
                                    ' None in _getproperties'))
             ntime = None
-            if self._p_.hascache('property', path):
-                ntime = time()
-                is_cached, props = self._p_.getcache('property', path, ntime)
+            if 'cache' in self and self._p_.hascache(path):
+                if 'expire' in self:
+                    ntime = int(time())
+                is_cached, props = self._p_.getcache(path, ntime)
                 if is_cached:
                     return props
             props = self._p_.getproperties(path, opt._properties)
             if is_apply_req:
                 props |= self.apply_requires(opt, path)
-            if 'expire' in self:
-                if ntime is None:
-                    ntime = time()
-                self._p_.setcache('property', path, props, ntime + expires_time)
+            if 'cache' in self:
+                if 'expire' in self:
+                    if  ntime is None:
+                        ntime = int(time())
+                    ntime = ntime + expires_time
+                self._p_.setcache(path, props, ntime)
         return props
 
     def append(self, propname):
@@ -342,13 +385,17 @@ class Settings(object):
     #____________________________________________________________
     def validate_properties(self, opt_or_descr, is_descr, is_write, path,
                             value=None, force_permissive=False,
-                            force_properties=None):
+                            force_properties=None, force_permissives=None):
         """
         validation upon the properties related to `opt_or_descr`
 
         :param opt_or_descr: an option or an option description object
         :param force_permissive: behaves as if the permissive property
                                  was present
+        :param force_properties: set() with properties that is force to add
+                                 in global properties
+        :param force_permissives: set() with permissives that is force to add
+                                 in global permissives
         :param is_descr: we have to know if we are in an option description,
                          just because the mandatory property
                          doesn't exist here
@@ -365,6 +412,8 @@ class Settings(object):
         self_properties = copy(self._getproperties())
         if force_permissive is True or 'permissive' in self_properties:
             properties -= self._p_.getpermissive()
+        if force_permissives is not None:
+            properties -= force_permissives
 
         # global properties
         if force_properties is not None:
@@ -402,15 +451,15 @@ class Settings(object):
     def setpermissive(self, permissive, opt=None, path=None):
         """
         enables us to put the permissives in the storage
-        
-        :param path: the option's path 
+
+        :param path: the option's path
         :param type: str
-        :param opt: if an option object is set, the path is extracted. 
-                    it is better (faster) to set the path parameter 
+        :param opt: if an option object is set, the path is extracted.
+                    it is better (faster) to set the path parameter
                     instead of passing a :class:`tiramisu.option.Option()` object.
         """
         if opt is not None and path is None:
-            path = self._get_opt_path(opt)
+            path = self._get_path_by_opt(opt)
         if not isinstance(permissive, tuple):
             raise TypeError(_('permissive must be a tuple'))
         self._p_.setpermissive(path, permissive)
@@ -433,18 +482,23 @@ class Settings(object):
             self.append(prop)
 
     def read_only(self):
-        "convenience method to freeze, hidde and disable"
+        "convenience method to freeze, hide and disable"
         self._read(ro_remove, ro_append)
 
     def read_write(self):
-        "convenience method to freeze, hidde and disable"
+        "convenience method to freeze, hide and disable"
         self._read(rw_remove, rw_append)
 
     def reset_cache(self, only_expired):
+        """reset all settings in cache
+
+        :param only_expired: if True reset only expired cached values
+        :type only_expired: boolean
+        """
         if only_expired:
-            self._p_.reset_expired_cache('property', time())
+            self._p_.reset_expired_cache(int(time()))
         else:
-            self._p_.reset_all_cache('property')
+            self._p_.reset_all_cache()
 
     def apply_requires(self, opt, path):
         """carries out the jit (just in time) requirements between options
@@ -456,12 +510,13 @@ class Settings(object):
 
         let's have a look at all the tuple's items:
 
-        - **option** is the target option's name or path
+        - **option** is the target option's
 
-        - **expected** is the target option's value that is going to trigger an action
+        - **expected** is the target option's value that is going to trigger
+          an action
 
-        - **action** is the (property) action to be accomplished if the target option
-          happens to have the expected value
+        - **action** is the (property) action to be accomplished if the target
+          option happens to have the expected value
 
         - if **inverse** is `True` and if the target option's value does not
           apply, then the property action must be removed from the option's
@@ -498,7 +553,7 @@ class Settings(object):
             for require in requires:
                 option, expected, action, inverse, \
                     transitive, same_action = require
-                reqpath = self._get_opt_path(option)
+                reqpath = self._get_path_by_opt(option)
                 if reqpath == path or reqpath.startswith(path + '.'):
                     raise RequirementError(_("malformed requirements "
                                              "imbrication detected for option:"
@@ -527,7 +582,32 @@ class Settings(object):
                     calc_properties.add(action)
                     # the calculation cannot be carried out
                     break
-            return calc_properties
+        return calc_properties
 
-    def _get_opt_path(self, opt):
+    def _get_path_by_opt(self, opt):
+        """just a wrapper to get path in optiondescription's cache
+
+        :param opt: `Option`'s object
+        :returns: path
+        """
         return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
+
+    def get_modified_properties(self):
+        return self._p_.get_modified_properties()
+
+    def get_modified_permissives(self):
+        return self._p_.get_modified_permissives()
+
+    def __getstate__(self):
+        return {'_p_': self._p_, '_owner': str(self._owner)}
+
+    def _impl_setstate(self, storage):
+        self._p_._storage = storage
+
+    def __setstate__(self, states):
+        self._p_ = states['_p_']
+        try:
+            self._owner = getattr(owners, states['_owner'])
+        except AttributeError:
+            owners.addowner(states['_owner'])
+            self._owner = getattr(owners, states['_owner'])
index e69de29..c232472 100644 (file)
@@ -0,0 +1,130 @@
+# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
+"""Config's informations are, by default, volatiles. This means, all values and
+settings changes will be lost.
+
+The storage is the system Tiramisu uses to communicate with various DB.
+You can specified a persistent storage.
+
+Storage is basic components used to set Config informations in DB.
+The primary "entry point" class is the StorageType and it's public
+configurator ``set_storage()``.
+"""
+
+
+from time import time
+from tiramisu.error import ConfigError
+from tiramisu.i18n import _
+
+
+class StorageType(object):
+    """Object to store storage's type. If a Config is already set,
+    default storage is store as selected storage. You cannot change it
+    after.
+    """
+    default_storage = 'dictionary'
+    storage_type = None
+    mod = None
+
+    def set(self, name):
+        if self.storage_type is not None:
+            if self.storage_type == name:
+                return
+            raise ConfigError(_('storage_type is already set, cannot rebind it'))
+        self.storage_type = name
+
+    def get(self):
+        if self.storage_type is None:
+            self.storage_type = self.default_storage
+        storage = self.storage_type
+        if self.mod is None:
+            modulepath = 'tiramisu.storage.{0}'.format(storage)
+            mod = __import__(modulepath)
+            for token in modulepath.split(".")[1:]:
+                mod = getattr(mod, token)
+            self.mod = mod
+        return self.mod
+
+
+storage_type = StorageType()
+
+
+def set_storage(name, **kwargs):
+    """Change storage's configuration
+
+    :params name: is the storage name. If storage is already set, cannot
+        reset storage name
+
+    Other attributes are differents according to the selected storage's name
+    """
+    storage_type.set(name)
+    setting = storage_type.get().setting
+    for option, value in kwargs.items():
+        try:
+            getattr(setting, option)
+            setattr(setting, option, value)
+        except AttributeError:
+            raise ValueError(_('option {0} not already exists in storage {1}'
+                               '').format(option, name))
+
+
+def _impl_getstate_setting():
+    setting = storage_type.get().setting
+    state = {'name': storage_type.storage_type}
+    for var in dir(setting):
+        if not var.startswith('_'):
+            state[var] = getattr(setting, var)
+    return state
+
+
+def get_storage(session_id, persistent, test):
+    """all used when __setstate__ a Config
+    """
+    return storage_type.get().Storage(session_id, persistent, test)
+
+
+def get_storages(context, session_id, persistent):
+    def gen_id(config):
+        return str(id(config)) + str(time())
+
+    if session_id is None:
+        session_id = gen_id(context)
+    imp = storage_type.get()
+    storage = imp.Storage(session_id, persistent)
+    return imp.Settings(storage), imp.Values(storage)
+
+
+def list_sessions():
+    """List all available session (persistent or not persistent)
+    """
+    return storage_type.get().list_sessions()
+
+
+def delete_session(session_id):
+    """Delete a selected session, be careful, you can deleted a session
+    use by an other instance
+    :params session_id: id of session to delete
+    """
+    return storage_type.get().delete_session(session_id)
+
+
+__all__ = (set_storage, list_sessions, delete_session)
index e69de29..bc81450 100644 (file)
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# ____________________________________________________________
+"""Default plugin for storage. All informations are store in a simple
+dictionary in memory.
+
+You cannot have persistente informations with this kind of storage.
+
+The advantage of this solution is that you can easily create a Config and
+use it. But if something goes wrong, you will lost your modifications.
+"""
+from .value import Values
+from .setting import Settings
+from .storage import setting, Storage, list_sessions, delete_session
+
+__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session)
index 580cba3..1b7001b 100644 (file)
@@ -17,7 +17,7 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 # ____________________________________________________________
-from tiramisu.storage.dictionary.storage import Cache
+from ..util import Cache
 
 
 class Settings(Cache):
@@ -50,12 +50,21 @@ class Settings(Cache):
         except KeyError:
             pass
 
-    def get_properties(self, context):
-        return self._properties
-
     # permissive
     def setpermissive(self, path, permissive):
         self._permissives[path] = frozenset(permissive)
 
     def getpermissive(self, path=None):
         return self._permissives.get(path, frozenset())
+
+    def get_modified_properties(self):
+        """return all modified settings in a dictionary
+        example: {'path1': set(['prop1', 'prop2'])}
+        """
+        return self._properties
+
+    def get_modified_permissives(self):
+        """return all modified permissives in a dictionary
+        example: {'path1': set(['perm1', 'perm2'])}
+        """
+        return self._permissives
index d4904c6..465fe26 100644 (file)
@@ -1,5 +1,4 @@
 # -*- coding: utf-8 -*-
-"default plugin for cache: set it in a simple dictionary"
 # Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
 #
 # This program is free software; you can redistribute it and/or modify
 # ____________________________________________________________
 from tiramisu.i18n import _
 from tiramisu.error import ConfigError
+from ..util import SerializeObject
 
 
-class Setting(object):
+class Setting(SerializeObject):
+    """Dictionary storage has no particular setting.
+    """
     pass
 
 
@@ -38,15 +40,18 @@ def delete_session(session_id):
 
 
 class Storage(object):
-    __slots__ = ('session_id',)
+    __slots__ = ('session_id', 'persistent')
     storage = 'dictionary'
+    #if object could be serializable
+    serializable = True
 
-    def __init__(self, session_id, persistent):
-        if session_id in _list_sessions:
+    def __init__(self, session_id, persistent, test=False):
+        if not test and session_id in _list_sessions:
             raise ValueError(_('session already used'))
         if persistent:
             raise ValueError(_('a dictionary cannot be persistent'))
         self.session_id = session_id
+        self.persistent = persistent
         _list_sessions.append(self.session_id)
 
     def __del__(self):
@@ -54,45 +59,3 @@ class Storage(object):
             _list_sessions.remove(self.session_id)
         except AttributeError:
             pass
-
-
-class Cache(object):
-    __slots__ = ('_cache', 'storage')
-    key_is_path = False
-
-    def __init__(self, storage):
-        self._cache = {}
-        self.storage = storage
-
-    def setcache(self, cache_type, path, val, time):
-        self._cache[path] = (val, time)
-
-    def getcache(self, cache_type, path, exp):
-        value, created = self._cache[path]
-        if exp < created:
-            return True, value
-        return False, None
-
-    def hascache(self, cache_type, path):
-        """ path is in the cache
-
-        :param cache_type: value | property
-        :param path: the path's option
-        """
-        return path in self._cache
-
-    def reset_expired_cache(self, cache_type, exp):
-        for key in tuple(self._cache.keys()):
-            val, created = self._cache[key]
-            if exp > created:
-                del(self._cache[key])
-
-    def reset_all_cache(self, cache_type):
-        "empty the cache"
-        self._cache.clear()
-
-    def get_cached(self, cache_type, context):
-        """return all values in a dictionary
-        example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')}
-        """
-        return self._cache
index 9c3e1f9..fedf1ec 100644 (file)
 #
 # ____________________________________________________________
 
-from tiramisu.storage.dictionary.storage import Cache
+from ..util import Cache
 
 
 class Values(Cache):
-    __slots__ = ('_values', '__weakref__')
+    __slots__ = ('_values', '_informations', '__weakref__')
 
     def __init__(self, storage):
         """init plugin means create values storage
         """
         self._values = {}
+        self._informations = {}
         # should init cache too
         super(Values, self).__init__(storage)
 
@@ -72,3 +73,22 @@ class Values(Cache):
         return: owner object
         """
         return self._values.get(path, (default, None))[0]
+
+    def set_information(self, key, value):
+        """updates the information's attribute
+        (which is a dictionary)
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._informations[key] = value
+
+    def get_information(self, key):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        if key in self._informations:
+            return self._informations[key]
+        else:
+            raise ValueError("not found")
index e69de29..8d79070 100644 (file)
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# ____________________________________________________________
+"""Sqlite3 plugin for storage. This storage is not made to be used in productive
+environment. It was developing as proof of concept.
+
+You should not configure differents Configs with same session_id.
+
+"""
+from .value import Values
+from .setting import Settings
+from .storage import setting, Storage, list_sessions, delete_session
+
+__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session)
index f91f9fd..ed79181 100644 (file)
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 # ____________________________________________________________
-from tiramisu.storage.sqlite3.storage import Cache
+from .sqlite3db import Sqlite3DB
 
 
-class Settings(Cache):
+class Settings(Sqlite3DB):
     __slots__ = tuple()
 
     def __init__(self, storage):
@@ -29,23 +29,23 @@ class Settings(Cache):
         permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path text '
         permissives_table += 'primary key, permissives text)'
         # should init cache too
-        super(Settings, self).__init__('property', storage)
-        self.storage.execute(settings_table, commit=False)
-        self.storage.execute(permissives_table)
+        super(Settings, self).__init__(storage)
+        self._storage.execute(settings_table, commit=False)
+        self._storage.execute(permissives_table)
 
     # propertives
     def setproperties(self, path, properties):
         path = self._sqlite_encode_path(path)
-        self.storage.execute("DELETE FROM property WHERE path = ?", (path,),
-                             False)
-        self.storage.execute("INSERT INTO property(path, properties) VALUES "
-                             "(?, ?)", (path,
-                                        self._sqlite_encode(properties)))
+        self._storage.execute("DELETE FROM property WHERE path = ?", (path,),
+                              False)
+        self._storage.execute("INSERT INTO property(path, properties) VALUES "
+                              "(?, ?)", (path,
+                                         self._sqlite_encode(properties)))
 
     def getproperties(self, path, default_properties):
         path = self._sqlite_encode_path(path)
-        value = self.storage.select("SELECT properties FROM property WHERE "
-                                    "path = ?", (path,))
+        value = self._storage.select("SELECT properties FROM property WHERE "
+                                     "path = ?", (path,))
         if value is None:
             return set(default_properties)
         else:
@@ -53,42 +53,53 @@ class Settings(Cache):
 
     def hasproperties(self, path):
         path = self._sqlite_encode_path(path)
-        return self.storage.select("SELECT properties FROM property WHERE "
-                                   "path = ?", (path,)) is not None
+        return self._storage.select("SELECT properties FROM property WHERE "
+                                    "path = ?", (path,)) is not None
 
     def reset_all_propertives(self):
-        self.storage.execute("DELETE FROM property")
+        self._storage.execute("DELETE FROM property")
 
     def reset_properties(self, path):
         path = self._sqlite_encode_path(path)
-        self.storage.execute("DELETE FROM property WHERE path = ?", (path,))
-
-    def get_properties(self, context):
-        """return all properties in a dictionary
-        """
-        ret = {}
-        for path, properties in self.storage.select("SELECT * FROM property",
-                                                    only_one=False):
-            path = self._sqlite_decode_path(path)
-            properties = self._sqlite_decode(properties)
-            ret[path] = properties
-        return ret
+        self._storage.execute("DELETE FROM property WHERE path = ?", (path,))
 
     # permissive
     def setpermissive(self, path, permissive):
         path = self._sqlite_encode_path(path)
-        self.storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
-                             False)
-        self.storage.execute("INSERT INTO permissive(path, permissives) "
-                             "VALUES (?, ?)", (path,
-                                               self._sqlite_encode(permissive)
-                                               ))
+        self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
+                              False)
+        self._storage.execute("INSERT INTO permissive(path, permissives) "
+                              "VALUES (?, ?)", (path,
+                                                self._sqlite_encode(permissive)
+                                                ))
 
     def getpermissive(self, path='_none'):
-        permissives = self.storage.select("SELECT permissives FROM "
-                                          "permissive WHERE path = ?",
-                                          (path,))
+        permissives = self._storage.select("SELECT permissives FROM "
+                                           "permissive WHERE path = ?",
+                                           (path,))
         if permissives is None:
             return frozenset()
         else:
             return frozenset(self._sqlite_decode(permissives[0]))
+
+    def get_modified_properties(self):
+        """return all modified settings in a dictionary
+        example: {'path1': set(['prop1', 'prop2'])}
+        """
+        ret = {}
+        for path, properties in self._storage.select("SELECT * FROM property",
+                                                     only_one=False):
+            path = self._sqlite_decode_path(path)
+            ret[path] = self._sqlite_decode(properties)
+        return ret
+
+    def get_modified_permissives(self):
+        """return all modified permissives in a dictionary
+        example: {'path1': set(['perm1', 'perm2'])}
+        """
+        ret = {}
+        for path, permissives in self._storage.select("SELECT * FROM permissive",
+                                                      only_one=False):
+            path = self._sqlite_decode_path(path)
+            ret[path] = self._sqlite_decode(permissives)
+        return ret
diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py
new file mode 100644 (file)
index 0000000..68f2886
--- /dev/null
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"sqlite3 cache"
+# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# ____________________________________________________________
+try:
+    from cPickle import loads, dumps
+except ImportError:
+    from pickle import loads, dumps
+from ..util import Cache
+
+
+class Sqlite3DB(Cache):
+    __slots__ = tuple()
+    def _sqlite_decode_path(self, path):
+        if path == '_none':
+            return None
+        else:
+            return path
+
+    def _sqlite_encode_path(self, path):
+        if path is None:
+            return '_none'
+        else:
+            return path
+
+    def _sqlite_decode(self, value):
+        return loads(value)
+
+    def _sqlite_encode(self, value):
+        if isinstance(value, list):
+            value = list(value)
+        return dumps(value)
index d855c88..3b4f265 100644 (file)
 #
 # ____________________________________________________________
 
-from pickle import dumps, loads
 from os import unlink
 from os.path import basename, splitext, join
 import sqlite3
 from glob import glob
+from ..util import SerializeObject
 
 
-class Setting(object):
+class Setting(SerializeObject):
+    """:param extension: database file extension (by default: db)
+    :param dir_database: root database directory (by default: /tmp)
+    """
     extension = 'db'
     dir_database = '/tmp'
 
@@ -50,13 +53,17 @@ def delete_session(session_id):
 
 
 class Storage(object):
-    __slots__ = ('_conn', '_cursor', 'persistent', '_session_id')
+    __slots__ = ('_conn', '_cursor', 'persistent', 'session_id', 'serializable')
     storage = 'sqlite3'
 
-    def __init__(self, session_id, persistent):
+    def __init__(self, session_id, persistent, test=False):
         self.persistent = persistent
-        self._session_id = session_id
-        self._conn = sqlite3.connect(_gen_filename(self._session_id))
+        if self.persistent:
+            self.serializable = True
+        else:
+            self.serializable = False
+        self.session_id = session_id
+        self._conn = sqlite3.connect(_gen_filename(self.session_id))
         self._conn.text_factory = str
         self._cursor = self._conn.cursor()
 
@@ -78,82 +85,4 @@ class Storage(object):
         self._cursor.close()
         self._conn.close()
         if not self.persistent:
-            delete_session(self._session_id)
-
-
-class Cache(object):
-    __slots__ = ('storage',)
-    key_is_path = True
-
-    def __init__(self, cache_type, storage):
-        self.storage = storage
-        cache_table = 'CREATE TABLE IF NOT EXISTS cache_{0}(path '.format(
-            cache_type)
-        cache_table += 'text primary key, value text, time real)'
-        self.storage.execute(cache_table)
-
-    # value
-    def _sqlite_decode_path(self, path):
-        if path == '_none':
-            return None
-        else:
-            return path
-
-    def _sqlite_encode_path(self, path):
-        if path is None:
-            return '_none'
-        else:
-            return path
-
-    def _sqlite_decode(self, value):
-        return loads(value)
-
-    def _sqlite_encode(self, value):
-        if isinstance(value, list):
-            value = list(value)
-        return dumps(value)
-
-    def setcache(self, cache_type, path, val, time):
-        convert_value = self._sqlite_encode(val)
-        path = self._sqlite_encode_path(path)
-        self.storage.execute("DELETE FROM cache_{0} WHERE path = ?".format(
-            cache_type), (path,), False)
-        self.storage.execute("INSERT INTO cache_{0}(path, value, time) "
-                             "VALUES (?, ?, ?)".format(cache_type),
-                             (path, convert_value, time))
-
-    def getcache(self, cache_type, path, exp):
-        path = self._sqlite_encode_path(path)
-        cached = self.storage.select("SELECT value FROM cache_{0} WHERE "
-                                     "path = ? AND time >= ?".format(
-                                         cache_type), (path, exp))
-        if cached is None:
-            return False, None
-        else:
-            return True, self._sqlite_decode(cached[0])
-
-    def hascache(self, cache_type, path):
-        path = self._sqlite_encode_path(path)
-        return self.storage.select("SELECT value FROM cache_{0} WHERE "
-                                   "path = ?".format(cache_type),
-                                   (path,)) is not None
-
-    def reset_expired_cache(self, cache_type, exp):
-        self.storage.execute("DELETE FROM cache_{0} WHERE time < ?".format(
-            cache_type), (exp,))
-
-    def reset_all_cache(self, cache_type):
-        self.storage.execute("DELETE FROM cache_{0}".format(cache_type))
-
-    def get_cached(self, cache_type, context):
-        """return all values in a dictionary
-        example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')}
-        """
-        ret = {}
-        for path, value, time in self.storage.select("SELECT * FROM cache_{0}"
-                                                     "".format(cache_type),
-                                                     only_one=False):
-            path = self._sqlite_decode_path(path)
-            value = self._sqlite_decode(value)
-            ret[path] = (value, time)
-        return ret
+            delete_session(self.session_id)
index 4207c43..672ecab 100644 (file)
 #
 # ____________________________________________________________
 
-from tiramisu.storage.sqlite3.storage import Cache
+from .sqlite3db import Sqlite3DB
 from tiramisu.setting import owners
 
 
-class Values(Cache):
+class Values(Sqlite3DB):
     __slots__ = ('__weakref__',)
 
     def __init__(self, storage):
         """init plugin means create values storage
         """
         # should init cache too
-        super(Values, self).__init__('value', storage)
+        super(Values, self).__init__(storage)
         values_table = 'CREATE TABLE IF NOT EXISTS value(path text primary '
         values_table += 'key, value text, owner text)'
-        self.storage.execute(values_table)
-        for owner in self.storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
+        self._storage.execute(values_table, commit=False)
+        informations_table = 'CREATE TABLE IF NOT EXISTS information(key text primary '
+        informations_table += 'key, value text)'
+        self._storage.execute(informations_table)
+        for owner in self._storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
             try:
                 getattr(owners, owner[0])
             except AttributeError:
@@ -41,7 +44,7 @@ class Values(Cache):
 
     # sqlite
     def _sqlite_select(self, path):
-        return self.storage.select("SELECT value FROM value WHERE path = ?",
+        return self._storage.select("SELECT value FROM value WHERE path = ?",
                                    (path,))
 
     # value
@@ -51,7 +54,7 @@ class Values(Cache):
         """
         self.resetvalue(path)
         path = self._sqlite_encode_path(path)
-        self.storage.execute("INSERT INTO value(path, value, owner) VALUES "
+        self._storage.execute("INSERT INTO value(path, value, owner) VALUES "
                              "(?, ?, ?)", (path, self._sqlite_encode(value),
                                            str(owner)))
 
@@ -73,14 +76,14 @@ class Values(Cache):
         """remove value means delete value in storage
         """
         path = self._sqlite_encode_path(path)
-        self.storage.execute("DELETE FROM value WHERE path = ?", (path,))
+        self._storage.execute("DELETE FROM value WHERE path = ?", (path,))
 
     def get_modified_values(self):
         """return all values in a dictionary
         example: {option1: (owner, 'value1'), option2: (owner, 'value2')}
         """
         ret = {}
-        for path, value, owner in self.storage.select("SELECT * FROM value",
+        for path, value, owner in self._storage.select("SELECT * FROM value",
                                                       only_one=False):
             path = self._sqlite_decode_path(path)
             owner = getattr(owners, owner)
@@ -94,7 +97,7 @@ class Values(Cache):
         """change owner for an option
         """
         path = self._sqlite_encode_path(path)
-        self.storage.execute("UPDATE value SET owner = ? WHERE path = ?",
+        self._storage.execute("UPDATE value SET owner = ? WHERE path = ?",
                              (str(owner), path))
 
     def getowner(self, path, default):
@@ -102,7 +105,7 @@ class Values(Cache):
         return: owner object
         """
         path = self._sqlite_encode_path(path)
-        owner = self.storage.select("SELECT owner FROM value WHERE path = ?",
+        owner = self._storage.select("SELECT owner FROM value WHERE path = ?",
                                     (path,))
         if owner is None:
             return default
@@ -114,3 +117,27 @@ class Values(Cache):
             except AttributeError:
                 owners.addowner(owner)
                 return getattr(owners, owner)
+
+    def set_information(self, key, value):
+        """updates the information's attribute
+        (which is a dictionary)
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._storage.execute("DELETE FROM information WHERE key = ?", (key,),
+                             False)
+        self._storage.execute("INSERT INTO information(key, value) VALUES "
+                             "(?, ?)", (key, self._sqlite_encode(value)))
+
+    def get_information(self, key):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        value = self._storage.select("SELECT value FROM information WHERE key = ?",
+                                    (key,))
+        if value is None:
+            raise ValueError("not found")
+        else:
+            return self._sqlite_decode(value[0])
diff --git a/tiramisu/storage/util.py b/tiramisu/storage/util.py
new file mode 100644 (file)
index 0000000..68482e6
--- /dev/null
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+"default plugin for cache: set it in a simple dictionary"
+# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# ____________________________________________________________
+from tiramisu.setting import owners
+
+
+class SerializeObject(object):
+    def __getstate__(self):
+        ret = {}
+        for key in dir(self):
+            if not key.startswith('__'):
+                ret[key] = getattr(self, key)
+        return ret
+
+
+class Cache(object):
+    __slots__ = ('_cache', '_storage')
+    key_is_path = False
+
+    def __init__(self, storage):
+        self._cache = {}
+        self._storage = storage
+
+    def __getstate__(self):
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        slots -= frozenset(['__weakref__', '_storage'])
+        states = {}
+        for slot in slots:
+            try:
+                value = getattr(self, slot)
+                #value has owners object, need 'str()' it
+                if slot == '_values':
+                    _value = {}
+                    for key, values in value.items():
+                        vals = list(values)
+                        vals[0] = str(vals[0])
+                        _value[key] = tuple(vals)
+                    states[slot] = _value
+                else:
+                    states[slot] = value
+            except AttributeError:
+                pass
+        return states
+
+    def __setstate__(self, states):
+        for key, value in states.items():
+            #value has owners object, need to reconstruct it
+            if key == '_values':
+                _value = {}
+                for key_, values_ in value.items():
+                    vals = list(values_)
+                    try:
+                        vals[0] = getattr(owners, vals[0])
+                    except AttributeError:
+                        owners.addowner(vals[0])
+                        vals[0] = getattr(owners, vals[0])
+                    _value[key_] = tuple(vals)
+                value = _value
+            setattr(self, key, value)
+
+    def setcache(self, path, val, time):
+        self._cache[path] = (val, time)
+
+    def getcache(self, path, exp):
+        value, created = self._cache[path]
+        if created is None or exp <= created:
+            return True, value
+        return False, None
+
+    def hascache(self, path):
+        """ path is in the cache
+
+        :param path: the path's option
+        """
+        return path in self._cache
+
+    def reset_expired_cache(self, exp):
+        for key in tuple(self._cache.keys()):
+            val, created = self._cache[key]
+            if created is not None and exp > created:
+                del(self._cache[key])
+
+    def reset_all_cache(self):
+        "empty the cache"
+        self._cache.clear()
+
+    def get_cached(self, context):
+        """return all values in a dictionary
+        example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')}
+        """
+        return self._cache
index d846bc4..d587de1 100644 (file)
@@ -44,9 +44,7 @@ class Values(object):
         """
         self.context = weakref.ref(context)
         # the storage type is dictionary or sqlite3
-        import_lib = 'tiramisu.storage.{0}.value'.format(storage.storage)
-        self._p_ = __import__(import_lib, globals(), locals(), ['Values'],
-                              ).Values(storage)
+        self._p_ = storage
 
     def _getdefault(self, opt):
         """
@@ -126,7 +124,7 @@ class Values(object):
             return True
         return False
 
-    def _getcallback_value(self, opt, index=None):
+    def _getcallback_value(self, opt, index=None, max_len=None):
         """
         retrieves a value for the options that have a callback
 
@@ -141,7 +139,7 @@ class Values(object):
         return carry_out_calculation(opt._name, config=self.context(),
                                      callback=callback,
                                      callback_params=callback_params,
-                                     index=index)
+                                     index=index, max_len=max_len)
 
     def __getitem__(self, opt):
         "enables us to use the pythonic dictionary-like access to values"
@@ -149,25 +147,28 @@ class Values(object):
 
     def getitem(self, opt, path=None, validate=True, force_permissive=False,
                 force_properties=None, validate_properties=True):
-        ntime = None
         if path is None:
             path = self._get_opt_path(opt)
-        if self._p_.hascache('value', path):
-            ntime = time()
-            is_cached, value = self._p_.getcache('value', path, ntime)
+        ntime = None
+        setting = self.context().cfgimpl_get_settings()
+        if 'cache' in setting and self._p_.hascache(path):
+            if 'expire' in setting:
+                ntime = int(time())
+            is_cached, value = self._p_.getcache(path, ntime)
             if is_cached:
                 if opt.impl_is_multi() and not isinstance(value, Multi):
                     #load value so don't need to validate if is not a Multi
                     value = Multi(value, self.context, opt, path, validate=False)
                 return value
-        val = self._getitem(opt, path, validate, force_permissive, force_properties,
-                            validate_properties)
-        if 'expire' in self.context().cfgimpl_get_settings() and validate and \
-                validate_properties and force_permissive is False and \
-                force_properties is None:
-            if ntime is None:
-                ntime = time()
-            self._p_.setcache('value', path, val, ntime + expires_time)
+        val = self._getitem(opt, path, validate, force_permissive,
+                            force_properties, validate_properties)
+        if 'cache' in setting and validate and validate_properties and \
+                force_permissive is False and force_properties is None:
+            if 'expire' in setting:
+                if ntime is None:
+                    ntime = int(time())
+                ntime = ntime + expires_time
+            self._p_.setcache(path, val, ntime)
 
         return val
 
@@ -176,11 +177,20 @@ class Values(object):
         # options with callbacks
         setting = self.context().cfgimpl_get_settings()
         is_frozen = 'frozen' in setting[opt]
+        # For calculating properties, we need value (ie for mandatory value).
+        # If value is calculating with a PropertiesOptionError's option
+        # _getcallback_value raise a ConfigError.
+        # We can not raise ConfigError if this option should raise
+        # PropertiesOptionError too. So we get config_error and raise
+        # ConfigError if properties did not raise.
+        config_error = None
+        force_permissives = None
         # if value is callback and is not set
         # or frozen with force_default_on_freeze
         if opt.impl_has_callback() and (
                 self._is_default_owner(path) or
                 (is_frozen and 'force_default_on_freeze' in setting[opt])):
+            lenmaster = None
             no_value_slave = False
             if (opt.impl_is_multi() and
                     opt.impl_get_multitype() == multitypes.slave):
@@ -192,15 +202,25 @@ class Values(object):
                     no_value_slave = True
 
             if not no_value_slave:
-                value = self._getcallback_value(opt)
-                if (opt.impl_is_multi() and
-                        opt.impl_get_multitype() == multitypes.slave):
-                    if not isinstance(value, list):
-                        value = [value for i in range(lenmaster)]
-            if opt.impl_is_multi():
-                value = Multi(value, self.context, opt, path, validate)
-            # suppress value if already set
-            self.reset(opt, path)
+                try:
+                    value = self._getcallback_value(opt, max_len=lenmaster)
+                except ConfigError as err:
+                    # cannot assign config_err directly in python 3.3
+                    config_error = err
+                    value = None
+                    # should not raise PropertiesOptionError if option is
+                    # mandatory
+                    force_permissives = set(['mandatory'])
+                else:
+                    if (opt.impl_is_multi() and
+                            opt.impl_get_multitype() == multitypes.slave):
+                        if not isinstance(value, list):
+                            value = [value for i in range(lenmaster)]
+            if config_error is None:
+                if opt.impl_is_multi():
+                    value = Multi(value, self.context, opt, path, validate)
+                # suppress value if already set
+                self.reset(opt, path)
         # frozen and force default
         elif is_frozen and 'force_default_on_freeze' in setting[opt]:
             value = self._getdefault(opt)
@@ -208,15 +228,18 @@ class Values(object):
                 value = Multi(value, self.context, opt, path, validate)
         else:
             value = self._getvalue(opt, path, validate)
-        if validate:
+        if config_error is None and validate:
             opt.impl_validate(value, self.context(), 'validator' in setting)
-        if self._is_default_owner(path) and \
+        if config_error is None and self._is_default_owner(path) and \
                 'force_store_value' in setting[opt]:
             self.setitem(opt, value, path, is_write=False)
         if validate_properties:
             setting.validate_properties(opt, False, False, value=value, path=path,
                                         force_permissive=force_permissive,
-                                        force_properties=force_properties)
+                                        force_properties=force_properties,
+                                        force_permissives=force_permissives)
+        if config_error is not None:
+            raise ConfigError(config_error)
         return value
 
     def __setitem__(self, opt, value):
@@ -230,7 +253,7 @@ class Values(object):
         opt.impl_validate(value, self.context(),
                           'validator' in self.context().cfgimpl_get_settings())
         if opt.impl_is_multi() and not isinstance(value, Multi):
-            value = Multi(value, self.context, opt, path)
+            value = Multi(value, self.context, opt, path, setitem=True)
         self._setvalue(opt, path, value, force_permissive=force_permissive,
                        is_write=is_write)
 
@@ -302,9 +325,9 @@ class Values(object):
         clears the cache if necessary
         """
         if only_expired:
-            self._p_.reset_expired_cache('value', time())
+            self._p_.reset_expired_cache(int(time()))
         else:
-            self._p_.reset_all_cache('value')
+            self._p_.reset_all_cache()
 
     def _get_opt_path(self, opt):
         """
@@ -315,6 +338,38 @@ class Values(object):
         """
         return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
 
+    # information
+    def set_information(self, key, value):
+        """updates the information's attribute
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._p_.set_information(key, value)
+
+    def get_information(self, key, default=None):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        try:
+            return self._p_.get_information(key)
+        except ValueError:
+            if default is not None:
+                return default
+            else:
+                raise ValueError(_("information's item"
+                                   " not found: {0}").format(key))
+
+    def __getstate__(self):
+        return {'_p_': self._p_}
+
+    def _impl_setstate(self, storage):
+        self._p_._storage = storage
+
+    def __setstate__(self, states):
+        self._p_ = states['_p_']
+
 # ____________________________________________________________
 # multi types
 
@@ -324,11 +379,13 @@ class Multi(list):
     that support item notation for the values of multi options"""
     __slots__ = ('opt', 'path', 'context')
 
-    def __init__(self, value, context, opt, path, validate=True):
+    def __init__(self, value, context, opt, path, validate=True,
+                 setitem=False):
         """
         :param value: the Multi wraps a list value
         :param context: the home config that has the values
         :param opt: the option object that have this Multi value
+        :param setitem: only if set a value
         """
         self.opt = opt
         self.path = path
@@ -338,27 +395,35 @@ class Multi(list):
         if not isinstance(value, list):
             value = [value]
         if validate and self.opt.impl_get_multitype() == multitypes.slave:
-            value = self._valid_slave(value)
-        elif self.opt.impl_get_multitype() == multitypes.master:
+            value = self._valid_slave(value, setitem)
+        elif validate and self.opt.impl_get_multitype() == multitypes.master:
             self._valid_master(value)
         super(Multi, self).__init__(value)
 
-    def _valid_slave(self, value):
+    def _valid_slave(self, value, setitem):
         #if slave, had values until master's one
+        values = self.context().cfgimpl_get_values()
         masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
             self.opt.impl_get_master_slaves())
         mastervalue = getattr(self.context(), masterp)
         masterlen = len(mastervalue)
         valuelen = len(value)
+        is_default_owner = not values._is_default_owner(self.path) or setitem
         if valuelen > masterlen or (valuelen < masterlen and
-                                    not self.context().cfgimpl_get_values(
-                                    )._is_default_owner(self.path)):
+                                    is_default_owner):
             raise SlaveError(_("invalid len for the slave: {0}"
                                " which has {1} as master").format(
                                    self.opt._name, masterp))
         elif valuelen < masterlen:
             for num in range(0, masterlen - valuelen):
-                value.append(self.opt.impl_getdefault_multi())
+                if self.opt.impl_has_callback():
+                    # if callback add a value, but this value will not change
+                    # anymore automaticly (because this value has owner)
+                    index = value.__len__()
+                    value.append(values._getcallback_value(self.opt,
+                                                           index=index))
+                else:
+                    value.append(self.opt.impl_getdefault_multi())
         #else: same len so do nothing
         return value
 
@@ -376,8 +441,17 @@ class Multi(list):
                                            self.opt._name, slave._name))
                 elif len(value_slave) < masterlen:
                     for num in range(0, masterlen - len(value_slave)):
-                        value_slave.append(slave.impl_getdefault_multi(),
-                                           force=True)
+                        if slave.impl_has_callback():
+                            # if callback add a value, but this value will not
+                            # change anymore automaticly (because this value
+                            # has owner)
+                            index = value_slave.__len__()
+                            value_slave.append(
+                                values._getcallback_value(slave, index=index),
+                                force=True)
+                        else:
+                            value_slave.append(slave.impl_getdefault_multi(),
+                                               force=True)
 
     def __setitem__(self, key, value):
         self._validate(value)
@@ -470,12 +544,15 @@ class Multi(list):
                                    "").format(str(value),
                                               self.opt._name, err))
 
-    def pop(self, key, force=False):
+    def pop(self, index, force=False):
         """the list value can be updated (poped)
         only if the option is a master
 
-        :param key: index of the element to pop
-        :return: the requested element
+        :param index: remove item a index
+        :type index: int
+        :param force: force pop item (withoud check master/slave)
+        :type force: boolean
+        :returns: item at index
         """
         if not force:
             if self.opt.impl_get_multitype() == multitypes.slave:
@@ -488,8 +565,8 @@ class Multi(list):
                         #get multi without valid properties
                         values.getitem(slave,
                                        validate_properties=False
-                                       ).pop(key, force=True)
+                                       ).pop(index, force=True)
         #set value without valid properties
-        ret = super(Multi, self).pop(key)
+        ret = super(Multi, self).pop(index)
         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
         return ret
index 7f1c10d..8bceb43 100644 (file)
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-07-18 15:20+CEST\n"
+"POT-Creation-Date: 2013-08-31 09:52+CEST\n"
 "PO-Revision-Date: \n"
 "Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -11,18 +11,18 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "X-Generator: Poedit 1.5.4\n"
 
-#: tiramisu/autolib.py:49
+#: tiramisu/autolib.py:58
 msgid "no config specified but needed"
 msgstr "aucune config spécifié alors que c'est nécessaire"
 
-#: tiramisu/autolib.py:56
+#: tiramisu/autolib.py:65
 msgid ""
 "unable to carry out a calculation, option {0} has properties: {1} for: {2}"
 msgstr ""
 "impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : "
 "{2}"
 
-#: tiramisu/autolib.py:65
+#: tiramisu/autolib.py:74
 msgid ""
 "unable to carry out a calculation, option value with multi types must have "
 "same length for: {0}"
@@ -30,86 +30,79 @@ msgstr ""
 "impossible d'effectuer le calcul, valeur d'un option avec le type multi doit "
 "avoir la même longueur pour : {0}"
 
-#: tiramisu/config.py:45
+#: tiramisu/config.py:47
 msgid "descr must be an optiondescription, not {0}"
 msgstr "descr doit être une optiondescription pas un {0}"
 
-#: tiramisu/config.py:118
+#: tiramisu/config.py:121
 msgid "unknown group_type: {0}"
 msgstr "group_type inconnu: {0}"
 
-#: tiramisu/config.py:154
-msgid "no optiondescription for this config (may be metaconfig without meta)"
+#: tiramisu/config.py:157
+msgid ""
+"no option description found for this config (may be metaconfig without meta)"
 msgstr ""
-"pas d'optiondescription pour cette config (par exemple metaconfig sans meta)"
+"pas d'option description pour cette config (peut être une metaconfig sans "
+"meta)"
 
-#: tiramisu/config.py:312
-msgid "unknown type_ type {0} for _find"
+#: tiramisu/config.py:311
+msgid "unknown type_ type {0}for _find"
 msgstr "type_ type {0} pour _find inconnu"
 
-#: tiramisu/config.py:351
+#: tiramisu/config.py:350
 msgid "no option found in config with these criteria"
 msgstr "aucune option trouvée dans la config avec ces critères"
 
-#: tiramisu/config.py:394
+#: tiramisu/config.py:400
 msgid "make_dict can't filtering with value without option"
 msgstr "make_dict ne peut filtrer sur une valeur mais sans option"
 
-#: tiramisu/config.py:414
+#: tiramisu/config.py:421
 msgid "unexpected path {0}, should start with {1}"
 msgstr "chemin imprévu {0}, devrait commencer par {1}"
 
-#: tiramisu/config.py:527
-msgid "metaconfig's children must be a list"
-msgstr "enfants d'une metaconfig doit être une liste"
-
-#: tiramisu/config.py:532
-msgid "metaconfig's children must be config, not {0}"
-msgstr "enfants d'une metaconfig doit être une config, pas {0}"
-
-#: tiramisu/config.py:537
-msgid "all config in metaconfig must have same optiondescription"
-msgstr ""
-"toutes les configs d'une metaconfig doivent avoir la même optiondescription"
-
-#: tiramisu/config.py:540
-msgid "child has already a metaconfig's"
-msgstr "enfant a déjà une metaconfig"
+#: tiramisu/config.py:481
+msgid "opt in getowner must be an option not {0}"
+msgstr "opt dans getowner doit être une option pas {0}"
 
-#: tiramisu/option.py:70
+#: tiramisu/option.py:71
 msgid "{0} has no attribute impl_set_information"
 msgstr "{0} n'a pas d'attribut impl_set_information"
 
-#: tiramisu/option.py:84
-msgid "Information's item not found: {0}"
-msgstr "l'élément information non trouvé: {0}"
-
 #: tiramisu/option.py:86
+msgid "information's item not found: {0}"
+msgstr "aucune config spécifié alors que c'est nécessaire"
+
+#: tiramisu/option.py:89
 msgid "{0} has no attribute impl_get_information"
 msgstr "{0} n'a pas d'attribut impl_get_information"
 
-#: tiramisu/option.py:124
+#: tiramisu/option.py:117
+msgid "'{0}' ({1}) object attribute '{2}' is read-only"
+msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seul"
+
+#: tiramisu/option.py:159
 msgid "invalid name: {0} for option"
 msgstr "nom invalide : {0} pour l'option"
 
-#: tiramisu/option.py:134
+#: tiramisu/option.py:169
 msgid "validator must be a function"
 msgstr "validator doit être une fonction"
 
-#: tiramisu/option.py:141
+#: tiramisu/option.py:176
 msgid "a default_multi is set whereas multi is False in option: {0}"
 msgstr ""
 "une default_multi est renseigné alors que multi est False dans l'option : {0}"
 
-#: tiramisu/option.py:147
+#: tiramisu/option.py:182
 msgid "invalid default_multi value {0} for option {1}: {2}"
 msgstr "la valeur default_multi est invalide {0} pour l'option {1} : {2}"
 
-#: tiramisu/option.py:150
+#: tiramisu/option.py:187
 msgid "default value not allowed if option: {0} is calculated"
 msgstr "la valeur par défaut n'est pas possible si l'option {0} est calculé"
 
-#: tiramisu/option.py:153
+#: tiramisu/option.py:190
 msgid ""
 "params defined for a callback function but no callback defined yet for "
 "option {0}"
@@ -117,183 +110,183 @@ msgstr ""
 "params définit pour une fonction callback mais par de callback défini encore "
 "pour l'option {0}"
 
-#: tiramisu/option.py:174 tiramisu/option.py:718
+#: tiramisu/option.py:212 tiramisu/option.py:753
 msgid "invalid properties type {0} for {1}, must be a tuple"
 msgstr "type des properties invalide {0} pour {1}, doit être un tuple"
 
-#: tiramisu/option.py:273
+#: tiramisu/option.py:285
 msgid "invalid value {0} for option {1} for object {2}"
 msgstr "valeur invalide {0} pour l'option {1} pour l'objet {2}"
 
-#: tiramisu/option.py:278 tiramisu/value.py:368
+#: tiramisu/option.py:293 tiramisu/value.py:468
 msgid "invalid value {0} for option {1}: {2}"
 msgstr "valeur invalide {0} pour l'option {1} : {2}"
 
-#: tiramisu/option.py:290
+#: tiramisu/option.py:305
 msgid "invalid value {0} for option {1} which must be a list"
 msgstr "valeur invalide {0} pour l'option {1} qui doit être une liste"
 
-#: tiramisu/option.py:354
+#: tiramisu/option.py:374
 msgid "invalid value {0} for option {1} must be different as {2} option"
 msgstr ""
 "valeur invalide {0} pour l'option {1} doit être différent que l'option {2}"
 
-#: tiramisu/option.py:376
+#: tiramisu/option.py:396
 msgid "values must be a tuple for {0}"
 msgstr "values doit être un tuple pour {0}"
 
-#: tiramisu/option.py:379
+#: tiramisu/option.py:399
 msgid "open_values must be a boolean for {0}"
 msgstr "open_values doit être un booléen pour {0}"
 
-#: tiramisu/option.py:400
+#: tiramisu/option.py:420
 msgid "value {0} is not permitted, only {1} is allowed"
 msgstr "valeur {0} n'est pas permit, seules {1} sont autorisées"
 
-#: tiramisu/option.py:411
+#: tiramisu/option.py:432
 msgid "value must be a boolean"
 msgstr "valeur doit être un booléen"
 
-#: tiramisu/option.py:421
+#: tiramisu/option.py:442
 msgid "value must be an integer"
 msgstr "valeur doit être un numbre"
 
-#: tiramisu/option.py:431
+#: tiramisu/option.py:452
 msgid "value must be a float"
 msgstr "valeur doit être un nombre flottant"
 
-#: tiramisu/option.py:441
-msgid "value must be a string"
-msgstr "valeur doit être une chaîne"
+#: tiramisu/option.py:462
+msgid "value must be a string, not {0}"
+msgstr "valeur doit être une chaîne, pas {0}"
 
-#: tiramisu/option.py:452
+#: tiramisu/option.py:480
 msgid "value must be an unicode"
 msgstr "valeur doit être une valeur unicode"
 
-#: tiramisu/option.py:463
+#: tiramisu/option.py:490
 msgid "malformed symlinkoption must be an option for symlink {0}"
 msgstr "symlinkoption mal formé doit être une option pour symlink {0}"
 
-#: tiramisu/option.py:497
-msgid "IP mustn't not be in reserved class"
+#: tiramisu/option.py:526
+msgid "IP shall not be in reserved class"
 msgstr "IP ne doit pas être d'une classe reservée"
 
-#: tiramisu/option.py:499
+#: tiramisu/option.py:528
 msgid "IP must be in private class"
 msgstr "IP doit être dans la classe privée"
 
-#: tiramisu/option.py:535
+#: tiramisu/option.py:566
 msgid "inconsistency in allowed range"
 msgstr "inconsistence dans la plage autorisée"
 
-#: tiramisu/option.py:540
+#: tiramisu/option.py:571
 msgid "max value is empty"
 msgstr "valeur maximum est vide"
 
-#: tiramisu/option.py:576
-msgid "network mustn't not be in reserved class"
+#: tiramisu/option.py:608
+msgid "network shall not be in reserved class"
 msgstr "réseau ne doit pas être dans la classe reservée"
 
-#: tiramisu/option.py:608
+#: tiramisu/option.py:640
 msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP"
 msgstr "réseau invalide {0} ({1}) avec masque {2} ({3}), ce réseau est une IP"
 
-#: tiramisu/option.py:612
+#: tiramisu/option.py:645
 msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network"
 msgstr "IP invalide {0} ({1}) avec masque {2} ({3}), cette IP est un réseau"
 
-#: tiramisu/option.py:617
+#: tiramisu/option.py:650
 msgid "invalid IP {0} ({1}) with netmask {2} ({3})"
 msgstr "IP invalide {0} ({1}) avec masque {2} ({3})"
 
-#: tiramisu/option.py:619
+#: tiramisu/option.py:652
 msgid "invalid network {0} ({1}) with netmask {2} ({3})"
 msgstr "réseau invalide {0} ({1}) avec masque {2} ({3})"
 
-#: tiramisu/option.py:639
+#: tiramisu/option.py:672
 msgid "unknown type_ {0} for hostname"
 msgstr "type_ inconnu {0} pour le nom d'hôte"
 
-#: tiramisu/option.py:642
+#: tiramisu/option.py:675
 msgid "allow_ip must be a boolean"
 msgstr "allow_ip doit être un booléen"
 
-#: tiramisu/option.py:671
+#: tiramisu/option.py:704
 msgid "invalid value for {0}, must have dot"
 msgstr "valeur invalide pour {0}, doit avoir un point"
 
-#: tiramisu/option.py:674
+#: tiramisu/option.py:707
 msgid "invalid domainname's length for {0} (max {1})"
 msgstr "longueur du nom de domaine invalide pour {0} (maximum {1})"
 
-#: tiramisu/option.py:676
+#: tiramisu/option.py:710
 msgid "invalid domainname's length for {0} (min 2)"
 msgstr "longueur du nom de domaine invalide pour {0} (minimum 2)"
 
-#: tiramisu/option.py:680
+#: tiramisu/option.py:714
 msgid "invalid domainname"
 msgstr "nom de domaine invalide"
 
-#: tiramisu/option.py:696
+#: tiramisu/option.py:731
 msgid "invalid name: {0} for optiondescription"
 msgstr "nom invalide : {0} pour l'optiondescription"
 
-#: tiramisu/option.py:707
+#: tiramisu/option.py:743
 msgid "duplicate option name: {0}"
 msgstr "nom de l'option dupliqué : {0}"
 
-#: tiramisu/option.py:731
+#: tiramisu/option.py:769
 msgid "unknown Option {0} in OptionDescription {1}"
 msgstr "Option {} inconnue pour l'OptionDescription{}"
 
-#: tiramisu/option.py:795
+#: tiramisu/option.py:820
 msgid "duplicate option: {0}"
 msgstr "option dupliquée : {0}"
 
-#: tiramisu/option.py:805
+#: tiramisu/option.py:850
 msgid "no option for path {0}"
 msgstr "pas d'option pour le chemin {0}"
 
-#: tiramisu/option.py:811
+#: tiramisu/option.py:856
 msgid "no option {0} found"
 msgstr "pas d'option {0} trouvée"
 
-#: tiramisu/option.py:821
+#: tiramisu/option.py:866
 msgid "cannot change group_type if already set (old {0}, new {1})"
 msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1})"
 
-#: tiramisu/option.py:833
+#: tiramisu/option.py:879
 msgid "master group {0} shall not have a subgroup"
 msgstr "groupe maître {0} ne doit pas avoir de sous-groupe"
 
-#: tiramisu/option.py:836
+#: tiramisu/option.py:882
 msgid "master group {0} shall not have a symlinkoption"
 msgstr "groupe maître {0} ne doit pas avoir de symlinkoption"
 
-#: tiramisu/option.py:839
+#: tiramisu/option.py:885
 msgid "not allowed option {0} in group {1}: this option is not a multi"
 msgstr ""
 "option non autorisée {0} dans le groupe {1} : cette option n'est pas une "
 "multi"
 
-#: tiramisu/option.py:849
+#: tiramisu/option.py:896
 msgid "master group with wrong master name for {0}"
 msgstr "le groupe maître avec un nom de maître éroné pour {0}"
 
-#: tiramisu/option.py:857
+#: tiramisu/option.py:905
 msgid "no child has same nom has master group for: {0}"
 msgstr "pas d'enfant avec le nom du groupe maître pour {0} "
 
-#: tiramisu/option.py:860
-msgid "not allowed group_type : {0}"
-msgstr "group_type non autorisé : {0}"
+#: tiramisu/option.py:908
+msgid "group_type: {0} not allowed"
+msgstr "group_type : {0} non autorisé"
 
-#: tiramisu/option.py:889
+#: tiramisu/option.py:946
 msgid "malformed requirements type for option: {0}, must be a dict"
 msgstr ""
 "type requirements malformé pour l'option : {0}, doit être un dictionnaire"
 
-#: tiramisu/option.py:905
+#: tiramisu/option.py:962
 msgid ""
 "malformed requirements for option: {0} require must have option, expected "
 "and action keys"
@@ -301,68 +294,87 @@ msgstr ""
 "requirements malformé pour l'option : {0} l'exigence doit avoir les clefs "
 "option, exptected et action"
 
-#: tiramisu/option.py:910
+#: tiramisu/option.py:967
 msgid "malformed requirements for option: {0} inverse must be boolean"
 msgstr "requirements malformé pour l'option : {0} inverse doit être un booléen"
 
-#: tiramisu/option.py:914
+#: tiramisu/option.py:971
 msgid "malformed requirements for option: {0} transitive must be boolean"
 msgstr "requirements malformé pour l'option : {0} transitive doit être booléen"
 
-#: tiramisu/option.py:918
+#: tiramisu/option.py:975
 msgid "malformed requirements for option: {0} same_action must be boolean"
 msgstr ""
 "requirements malformé pour l'option : {0} same_action doit être un booléen"
 
-#: tiramisu/option.py:923
+#: tiramisu/option.py:979
 msgid "malformed requirements must be an option in option {0}"
 msgstr "requirements malformé doit être une option dans l'option {0}"
 
-#: tiramisu/option.py:926
+#: tiramisu/option.py:982
 msgid "malformed requirements option {0} should not be a multi"
 msgstr "requirements malformé l'option {0} ne doit pas être une multi"
 
-#: tiramisu/option.py:932
+#: tiramisu/option.py:988
 msgid ""
 "malformed requirements second argument must be valid for option {0}: {1}"
 msgstr ""
 "requirements malformé deuxième argument doit être valide pour l'option {0} : "
 "{1}"
 
-#: tiramisu/option.py:936
+#: tiramisu/option.py:993
 msgid "inconsistency in action types for option: {0} action: {1}"
 msgstr "incohérence dans les types action pour l'option : {0} action {1}"
 
-#: tiramisu/setting.py:45
-msgid "can't rebind group ({})"
-msgstr "ne peut reconsolider un groupe ({0})"
+#: tiramisu/setting.py:47
+msgid "storage_type is already set, cannot rebind it"
+msgstr "storage_type est déjà défini, impossible de le redéfinir"
+
+#: tiramisu/setting.py:67
+msgid "can't rebind {0}"
+msgstr "ne peut redéfinir ({0})"
 
-#: tiramisu/setting.py:50
-msgid "can't unbind group ({})"
-msgstr "ne peut délier un groupe ({0})"
+#: tiramisu/setting.py:72
+msgid "can't unbind {0}"
+msgstr "ne peut supprimer ({0})"
+
+#: tiramisu/setting.py:185
+msgid "cannot append {0} property for option {1}: this property is calculated"
+msgstr ""
+"ne peut ajouter la propriété {0} dans l'option {1}: cette propriété est "
+"calculée"
 
-#: tiramisu/setting.py:210
+#: tiramisu/setting.py:215
+msgid "option {0} not already exists in storage {1}"
+msgstr "option {0} n'existe pas dans l'espace de stockage {1}"
+
+#: tiramisu/setting.py:282
 msgid "opt and all_properties must not be set together in reset"
 msgstr "opt et all_properties ne doit pas être renseigné ensemble dans reset"
 
-#: tiramisu/setting.py:294
+#: tiramisu/setting.py:297
+msgid "if opt is not None, path should not be None in _getproperties"
+msgstr ""
+"si opt n'est pas None, path devrait ne pas être à None dans _getproperties"
+
+#: tiramisu/setting.py:391
 msgid "cannot change the value for option {0} this option is frozen"
 msgstr ""
 "ne peut modifié la valeur de l'option {0} cette option n'est pas modifiable"
 
-#: tiramisu/setting.py:298
+#: tiramisu/setting.py:397
 msgid "trying to access to an option named: {0} with properties {1}"
 msgstr "tentative d'accès à une option nommée : {0} avec les propriétés {1}"
 
-#: tiramisu/setting.py:307
+#: tiramisu/setting.py:415
 msgid "permissive must be a tuple"
 msgstr "permissive doit être un tuple"
 
-#: tiramisu/setting.py:314 tiramisu/value.py:208
+#: tiramisu/setting.py:422 tiramisu/value.py:277
 msgid "invalid generic owner {0}"
 msgstr "invalide owner générique {0}"
 
-#: tiramisu/setting.py:367
+#: tiramisu/setting.py:503
 msgid ""
 "malformed requirements imbrication detected for option: '{0}' with "
 "requirement on: '{1}'"
@@ -370,48 +382,81 @@ msgstr ""
 "imbrication de requirements malformé detectée pour l'option : '{0}' avec "
 "requirement sur : '{1}'"
 
-#: tiramisu/setting.py:377
+#: tiramisu/setting.py:515
 msgid "option '{0}' has requirement's property error: {1} {2}"
 msgstr "l'option '{0}' a une erreur de propriété pour le requirement : {1} {2}"
 
-#: tiramisu/setting.py:383
-msgid "required option not found: {0}"
-msgstr "option requise non trouvée : {0}"
+#: tiramisu/storage/dictionary/storage.py:37
+msgid "dictionary storage cannot delete session"
+msgstr ""
+"impossible de supprimer une session dans un espace de stockage dictionary"
+
+#: tiramisu/storage/dictionary/storage.py:46
+msgid "session already used"
+msgstr "session déjà utilisée"
+
+#: tiramisu/storage/dictionary/storage.py:48
+msgid "a dictionary cannot be persistent"
+msgstr "un espace de stockage dictionary ne peut être persistant"
 
-#: tiramisu/value.py:206
+#: tiramisu/value.py:284
 msgid "no value for {0} cannot change owner to {1}"
 msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {1}"
 
-#: tiramisu/value.py:270
+#: tiramisu/value.py:356
 msgid "invalid len for the slave: {0} which has {1} as master"
 msgstr "longueur invalide pour une esclave : {0} qui a {1} comme maître"
 
-#: tiramisu/value.py:286
+#: tiramisu/value.py:373
 msgid "invalid len for the master: {0} which has {1} as slave with greater len"
 msgstr ""
 "longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus "
 "grande longueur"
 
-#: tiramisu/value.py:306
+#: tiramisu/value.py:394
 msgid "cannot append a value on a multi option {0} which is a slave"
 msgstr "ne peut ajouter une valeur sur l'option multi {0} qui est une esclave"
 
-#: tiramisu/value.py:334
+#: tiramisu/value.py:429
 msgid "cannot sort multi option {0} if master or slave"
 msgstr "ne peut trier une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:342
+#: tiramisu/value.py:433
+msgid "cmp is not permitted in python v3 or greater"
+msgstr "cmp n'est pas permis en python v3 ou supérieure"
+
+#: tiramisu/value.py:442
 msgid "cannot reverse multi option {0} if master or slave"
 msgstr "ne peut inverser une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:350
+#: tiramisu/value.py:450
 msgid "cannot insert multi option {0} if master or slave"
 msgstr "ne peut insérer une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:358
+#: tiramisu/value.py:458
 msgid "cannot extend multi option {0} if master or slave"
 msgstr "ne peut étendre une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:381
+#: tiramisu/value.py:482
 msgid "cannot pop a value on a multi option {0} which is a slave"
 msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave"
+
+#~ msgid "metaconfig's children must be a list"
+#~ msgstr "enfants d'une metaconfig doit être une liste"
+
+#~ msgid "metaconfig's children must be config, not {0}"
+#~ msgstr "enfants d'une metaconfig doit être une config, pas {0}"
+
+#~ msgid "all config in metaconfig must have same optiondescription"
+#~ msgstr ""
+#~ "toutes les configs d'une metaconfig doivent avoir la même "
+#~ "optiondescription"
+
+#~ msgid "child has already a metaconfig's"
+#~ msgstr "enfant a déjà une metaconfig"
+
+#~ msgid "not allowed group_type : {0}"
+#~ msgstr "group_type non autorisé : {0}"
+
+#~ msgid "required option not found: {0}"
+#~ msgstr "option requise non trouvée : {0}"
index faed4f3..ef40426 100644 (file)
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2013-07-18 15:20+CEST\n"
+"POT-Creation-Date: 2013-09-02 11:30+CEST\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -15,375 +15,395 @@ msgstr ""
 "Generated-By: pygettext.py 1.5\n"
 
 
-#: tiramisu/autolib.py:49
+#: tiramisu/autolib.py:58
 msgid "no config specified but needed"
 msgstr ""
 
-#: tiramisu/autolib.py:56
+#: tiramisu/autolib.py:65
 msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}"
 msgstr ""
 
-#: tiramisu/autolib.py:65
+#: tiramisu/autolib.py:74
 msgid "unable to carry out a calculation, option value with multi types must have same length for: {0}"
 msgstr ""
 
-#: tiramisu/config.py:45
+#: tiramisu/config.py:47
 msgid "descr must be an optiondescription, not {0}"
 msgstr ""
 
-#: tiramisu/config.py:118
+#: tiramisu/config.py:121
 msgid "unknown group_type: {0}"
 msgstr ""
 
-#: tiramisu/config.py:154
-msgid "no optiondescription for this config (may be metaconfig without meta)"
+#: tiramisu/config.py:157
+msgid "no option description found for this config (may be metaconfig without meta)"
 msgstr ""
 
-#: tiramisu/config.py:312
-msgid "unknown type_ type {0} for _find"
+#: tiramisu/config.py:311
+msgid "unknown type_ type {0}for _find"
 msgstr ""
 
-#: tiramisu/config.py:351
+#: tiramisu/config.py:350
 msgid "no option found in config with these criteria"
 msgstr ""
 
-#: tiramisu/config.py:394
+#: tiramisu/config.py:400
 msgid "make_dict can't filtering with value without option"
 msgstr ""
 
-#: tiramisu/config.py:414
+#: tiramisu/config.py:421
 msgid "unexpected path {0}, should start with {1}"
 msgstr ""
 
-#: tiramisu/config.py:527
-msgid "metaconfig's children must be a list"
+#: tiramisu/config.py:481
+msgid "opt in getowner must be an option not {0}"
 msgstr ""
 
-#: tiramisu/config.py:532
-msgid "metaconfig's children must be config, not {0}"
-msgstr ""
-
-#: tiramisu/config.py:537
-msgid "all config in metaconfig must have same optiondescription"
-msgstr ""
-
-#: tiramisu/config.py:540
-msgid "child has already a metaconfig's"
-msgstr ""
-
-#: tiramisu/option.py:70
+#: tiramisu/option.py:71
 msgid "{0} has no attribute impl_set_information"
 msgstr ""
 
-#: tiramisu/option.py:84
-msgid "Information's item not found: {0}"
+#: tiramisu/option.py:86
+msgid "information's item not found: {0}"
 msgstr ""
 
-#: tiramisu/option.py:86
+#: tiramisu/option.py:89
 msgid "{0} has no attribute impl_get_information"
 msgstr ""
 
-#: tiramisu/option.py:124
+#: tiramisu/option.py:117
+msgid "'{0}' ({1}) object attribute '{2}' is read-only"
+msgstr ""
+
+#: tiramisu/option.py:208
 msgid "invalid name: {0} for option"
 msgstr ""
 
-#: tiramisu/option.py:134
+#: tiramisu/option.py:218
 msgid "validator must be a function"
 msgstr ""
 
-#: tiramisu/option.py:141
+#: tiramisu/option.py:225
 msgid "a default_multi is set whereas multi is False in option: {0}"
 msgstr ""
 
-#: tiramisu/option.py:147
+#: tiramisu/option.py:231
 msgid "invalid default_multi value {0} for option {1}: {2}"
 msgstr ""
 
-#: tiramisu/option.py:150
+#: tiramisu/option.py:236
 msgid "default value not allowed if option: {0} is calculated"
 msgstr ""
 
-#: tiramisu/option.py:153
+#: tiramisu/option.py:239
 msgid "params defined for a callback function but no callback defined yet for option {0}"
 msgstr ""
 
-#: tiramisu/option.py:174 tiramisu/option.py:718
+#: tiramisu/option.py:261 tiramisu/option.py:809
 msgid "invalid properties type {0} for {1}, must be a tuple"
 msgstr ""
 
-#: tiramisu/option.py:273
+#: tiramisu/option.py:334
 msgid "invalid value {0} for option {1} for object {2}"
 msgstr ""
 
-#: tiramisu/option.py:278 tiramisu/value.py:368
+#: tiramisu/option.py:342 tiramisu/value.py:468
 msgid "invalid value {0} for option {1}: {2}"
 msgstr ""
 
-#: tiramisu/option.py:290
+#: tiramisu/option.py:354
 msgid "invalid value {0} for option {1} which must be a list"
 msgstr ""
 
-#: tiramisu/option.py:354
+#: tiramisu/option.py:423
 msgid "invalid value {0} for option {1} must be different as {2} option"
 msgstr ""
 
-#: tiramisu/option.py:376
+#: tiramisu/option.py:445
 msgid "values must be a tuple for {0}"
 msgstr ""
 
-#: tiramisu/option.py:379
+#: tiramisu/option.py:448
 msgid "open_values must be a boolean for {0}"
 msgstr ""
 
-#: tiramisu/option.py:400
+#: tiramisu/option.py:469
 msgid "value {0} is not permitted, only {1} is allowed"
 msgstr ""
 
-#: tiramisu/option.py:411
+#: tiramisu/option.py:481
 msgid "value must be a boolean"
 msgstr ""
 
-#: tiramisu/option.py:421
+#: tiramisu/option.py:491
 msgid "value must be an integer"
 msgstr ""
 
-#: tiramisu/option.py:431
+#: tiramisu/option.py:501
 msgid "value must be a float"
 msgstr ""
 
-#: tiramisu/option.py:441
-msgid "value must be a string"
+#: tiramisu/option.py:511
+msgid "value must be a string, not {0}"
 msgstr ""
 
-#: tiramisu/option.py:452
+#: tiramisu/option.py:529
 msgid "value must be an unicode"
 msgstr ""
 
-#: tiramisu/option.py:463
+#: tiramisu/option.py:539
 msgid "malformed symlinkoption must be an option for symlink {0}"
 msgstr ""
 
-#: tiramisu/option.py:497
-msgid "IP mustn't not be in reserved class"
+#: tiramisu/option.py:581
+msgid "IP shall not be in reserved class"
 msgstr ""
 
-#: tiramisu/option.py:499
+#: tiramisu/option.py:583
 msgid "IP must be in private class"
 msgstr ""
 
-#: tiramisu/option.py:535
+#: tiramisu/option.py:621
 msgid "inconsistency in allowed range"
 msgstr ""
 
-#: tiramisu/option.py:540
+#: tiramisu/option.py:626
 msgid "max value is empty"
 msgstr ""
 
-#: tiramisu/option.py:576
-msgid "network mustn't not be in reserved class"
+#: tiramisu/option.py:663
+msgid "network shall not be in reserved class"
 msgstr ""
 
-#: tiramisu/option.py:608
+#: tiramisu/option.py:695
 msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP"
 msgstr ""
 
-#: tiramisu/option.py:612
+#: tiramisu/option.py:700
 msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network"
 msgstr ""
 
-#: tiramisu/option.py:617
+#: tiramisu/option.py:705
 msgid "invalid IP {0} ({1}) with netmask {2} ({3})"
 msgstr ""
 
-#: tiramisu/option.py:619
+#: tiramisu/option.py:707
 msgid "invalid network {0} ({1}) with netmask {2} ({3})"
 msgstr ""
 
-#: tiramisu/option.py:639
+#: tiramisu/option.py:727
 msgid "unknown type_ {0} for hostname"
 msgstr ""
 
-#: tiramisu/option.py:642
+#: tiramisu/option.py:730
 msgid "allow_ip must be a boolean"
 msgstr ""
 
-#: tiramisu/option.py:671
+#: tiramisu/option.py:759
 msgid "invalid value for {0}, must have dot"
 msgstr ""
 
-#: tiramisu/option.py:674
+#: tiramisu/option.py:762
 msgid "invalid domainname's length for {0} (max {1})"
 msgstr ""
 
-#: tiramisu/option.py:676
+#: tiramisu/option.py:765
 msgid "invalid domainname's length for {0} (min 2)"
 msgstr ""
 
-#: tiramisu/option.py:680
+#: tiramisu/option.py:769
 msgid "invalid domainname"
 msgstr ""
 
-#: tiramisu/option.py:696
+#: tiramisu/option.py:787
 msgid "invalid name: {0} for optiondescription"
 msgstr ""
 
-#: tiramisu/option.py:707
+#: tiramisu/option.py:799
 msgid "duplicate option name: {0}"
 msgstr ""
 
-#: tiramisu/option.py:731
+#: tiramisu/option.py:825
 msgid "unknown Option {0} in OptionDescription {1}"
 msgstr ""
 
-#: tiramisu/option.py:795
+#: tiramisu/option.py:874
 msgid "duplicate option: {0}"
 msgstr ""
 
-#: tiramisu/option.py:805
+#: tiramisu/option.py:904
 msgid "no option for path {0}"
 msgstr ""
 
-#: tiramisu/option.py:811
+#: tiramisu/option.py:910
 msgid "no option {0} found"
 msgstr ""
 
-#: tiramisu/option.py:821
+#: tiramisu/option.py:920
 msgid "cannot change group_type if already set (old {0}, new {1})"
 msgstr ""
 
-#: tiramisu/option.py:833
+#: tiramisu/option.py:933
 msgid "master group {0} shall not have a subgroup"
 msgstr ""
 
-#: tiramisu/option.py:836
+#: tiramisu/option.py:936
 msgid "master group {0} shall not have a symlinkoption"
 msgstr ""
 
-#: tiramisu/option.py:839
+#: tiramisu/option.py:939
 msgid "not allowed option {0} in group {1}: this option is not a multi"
 msgstr ""
 
-#: tiramisu/option.py:849
+#: tiramisu/option.py:950
 msgid "master group with wrong master name for {0}"
 msgstr ""
 
-#: tiramisu/option.py:857
+#: tiramisu/option.py:959
 msgid "no child has same nom has master group for: {0}"
 msgstr ""
 
-#: tiramisu/option.py:860
-msgid "not allowed group_type : {0}"
+#: tiramisu/option.py:962
+msgid "group_type: {0} not allowed"
 msgstr ""
 
-#: tiramisu/option.py:889
+#: tiramisu/option.py:1021
 msgid "malformed requirements type for option: {0}, must be a dict"
 msgstr ""
 
-#: tiramisu/option.py:905
+#: tiramisu/option.py:1037
 msgid "malformed requirements for option: {0} require must have option, expected and action keys"
 msgstr ""
 
-#: tiramisu/option.py:910
+#: tiramisu/option.py:1042
 msgid "malformed requirements for option: {0} inverse must be boolean"
 msgstr ""
 
-#: tiramisu/option.py:914
+#: tiramisu/option.py:1046
 msgid "malformed requirements for option: {0} transitive must be boolean"
 msgstr ""
 
-#: tiramisu/option.py:918
+#: tiramisu/option.py:1050
 msgid "malformed requirements for option: {0} same_action must be boolean"
 msgstr ""
 
-#: tiramisu/option.py:923
+#: tiramisu/option.py:1054
 msgid "malformed requirements must be an option in option {0}"
 msgstr ""
 
-#: tiramisu/option.py:926
+#: tiramisu/option.py:1057
 msgid "malformed requirements option {0} should not be a multi"
 msgstr ""
 
-#: tiramisu/option.py:932
+#: tiramisu/option.py:1063
 msgid "malformed requirements second argument must be valid for option {0}: {1}"
 msgstr ""
 
-#: tiramisu/option.py:936
+#: tiramisu/option.py:1068
 msgid "inconsistency in action types for option: {0} action: {1}"
 msgstr ""
 
-#: tiramisu/setting.py:45
-msgid "can't rebind group ({})"
+#: tiramisu/setting.py:47
+msgid "storage_type is already set, cannot rebind it"
+msgstr ""
+
+#: tiramisu/setting.py:67
+msgid "can't rebind {0}"
+msgstr ""
+
+#: tiramisu/setting.py:72
+msgid "can't unbind {0}"
+msgstr ""
+
+#: tiramisu/setting.py:185
+msgid "cannot append {0} property for option {1}: this property is calculated"
 msgstr ""
 
-#: tiramisu/setting.py:50
-msgid "can't unbind group ({})"
+#: tiramisu/setting.py:215
+msgid "option {0} not already exists in storage {1}"
 msgstr ""
 
-#: tiramisu/setting.py:210
+#: tiramisu/setting.py:282
 msgid "opt and all_properties must not be set together in reset"
 msgstr ""
 
-#: tiramisu/setting.py:294
+#: tiramisu/setting.py:297
+msgid "if opt is not None, path should not be None in _getproperties"
+msgstr ""
+
+#: tiramisu/setting.py:391
 msgid "cannot change the value for option {0} this option is frozen"
 msgstr ""
 
-#: tiramisu/setting.py:298
+#: tiramisu/setting.py:397
 msgid "trying to access to an option named: {0} with properties {1}"
 msgstr ""
 
-#: tiramisu/setting.py:307
+#: tiramisu/setting.py:415
 msgid "permissive must be a tuple"
 msgstr ""
 
-#: tiramisu/setting.py:314 tiramisu/value.py:208
+#: tiramisu/setting.py:422 tiramisu/value.py:277
 msgid "invalid generic owner {0}"
 msgstr ""
 
-#: tiramisu/setting.py:367
+#: tiramisu/setting.py:503
 msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'"
 msgstr ""
 
-#: tiramisu/setting.py:377
+#: tiramisu/setting.py:515
 msgid "option '{0}' has requirement's property error: {1} {2}"
 msgstr ""
 
-#: tiramisu/setting.py:383
-msgid "required option not found: {0}"
+#: tiramisu/storage/dictionary/storage.py:37
+msgid "dictionary storage cannot delete session"
+msgstr ""
+
+#: tiramisu/storage/dictionary/storage.py:46
+msgid "session already used"
 msgstr ""
 
-#: tiramisu/value.py:206
+#: tiramisu/storage/dictionary/storage.py:48
+msgid "a dictionary cannot be persistent"
+msgstr ""
+
+#: tiramisu/value.py:284
 msgid "no value for {0} cannot change owner to {1}"
 msgstr ""
 
-#: tiramisu/value.py:270
+#: tiramisu/value.py:356
 msgid "invalid len for the slave: {0} which has {1} as master"
 msgstr ""
 
-#: tiramisu/value.py:286
+#: tiramisu/value.py:373
 msgid "invalid len for the master: {0} which has {1} as slave with greater len"
 msgstr ""
 
-#: tiramisu/value.py:306
+#: tiramisu/value.py:394
 msgid "cannot append a value on a multi option {0} which is a slave"
 msgstr ""
 
-#: tiramisu/value.py:334
+#: tiramisu/value.py:429
 msgid "cannot sort multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:342
+#: tiramisu/value.py:433
+msgid "cmp is not permitted in python v3 or greater"
+msgstr ""
+
+#: tiramisu/value.py:442
 msgid "cannot reverse multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:350
+#: tiramisu/value.py:450
 msgid "cannot insert multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:358
+#: tiramisu/value.py:458
 msgid "cannot extend multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:381
+#: tiramisu/value.py:482
 msgid "cannot pop a value on a multi option {0} which is a slave"
 msgstr ""