Merge branch 'master' into lgpl

Conflicts:
	setup.py
This commit is contained in:
Emmanuel Garette 2013-09-22 22:01:19 +02:00
commit 4a44068a23
49 changed files with 3412 additions and 1022 deletions

View File

@ -6,3 +6,5 @@ Emmanuel Garette <egarette@cadoles.com> developer
Daniel Dehennin <daniel.dehennin@ac-dijon.fr> contributor Daniel Dehennin <daniel.dehennin@ac-dijon.fr> contributor
Philippe Caseiro <pcaseiro@cadoles.com> contributor Philippe Caseiro <pcaseiro@cadoles.com> contributor
Gerald Schwartzmann <gschartzmann@cadoles.com> tiramisu's logo (made with The Gimp)

View File

@ -15,19 +15,28 @@ ifneq ($(DESTDIR),)
PYTHON_OPTS += --root $(DESTDIR) PYTHON_OPTS += --root $(DESTDIR)
endif endif
LAST_TAG := $(shell git describe --tags --abbrev=0) VERSION := `cat VERSION`
VERSION := $(shell echo $(LAST_TAG) | awk -F'/' '{print $$2}' || true)
VERSION_FILE := version.in 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 # Build translation files
define build_translation define build_translation
if [ -d ${1} ]; then \ if [ -d ${1} ]; then \
for f in `find ${1} -name "*.po"`; do \ 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; \ done; \
fi fi
endef endef
# Install Traduction # Install Traduction
define install_translation define install_translation
if [ -d ${1} ]; then \ if [ -d ${1} ]; then \
@ -39,38 +48,33 @@ define install_translation
fi fi
endef endef
all: all: build-lang
clean: clean:
$(RM) -r build $(RM) -r build
$(RM) -r tiramisu.egg-info/ $(RM) -r $(PACKAGE).egg-info/
$(RM) -r $(TRADUC_DIR)/*/*.mo $(RM) -r $(TRADUC_DIR)/*/*.mo
#test: clean #test: clean
# py.test # py.test
# Build or update Portable Object Base Translation for gettext
build-pot:
$(call gettext)
build-lang: build-lang:
$(call build_translation, $(TRADUC_DIR)) $(call build_translation, $(TRADUC_DIR))
install-lang: build-lang install-lang:
$(INSTALL_DIR) $(TRADUC_DEST) $(INSTALL_DIR) $(TRADUC_DEST)
$(call install_translation, $(TRADUC_DIR)) $(call install_translation, $(TRADUC_DIR))
install: version.in install-lang install: install-lang
python setup.py install --no-compile $(PYTHON_OPTS) 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 # List in .PHONY to force generation at each call
version.in: .PHONY: all clean build-pot build-lang install-lang install dist
@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

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.0rc1

View File

@ -11,4 +11,5 @@ Auto generated library's API
tiramisu.value tiramisu.value
tiramisu.autolib tiramisu.autolib
tiramisu.error tiramisu.error
tiramisu.storage

BIN
doc/config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

257
doc/config.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -6,15 +6,17 @@ Options handling basics
Tiramisu is made of almost three main objects : 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.Option` stands for the option types
- :class:`tiramisu.option.OptionDescription` is the shema, the option's structure - :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 Accessing the `Option`'s
------------------------- -------------------------
The :class:`~tiramisu.config.Config` object attribute access notation stands for 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, :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 and the value is the value accessed by the `__getattr__` attribute access
mechanism. 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 as does every option group. The parts of the full name of the option are
separated by dots: e.g. ``cfg.optgroup.optname``. 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): (because explicit is better than implicit):
1. If the option has not been declared, an `AttributeError` is raised, 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 :term:`mandatory option`. A mandatory option is an option that must have a value
defined. defined.
Appart from this case, if no value have been set yet, the value is `None`. When Setting the value of an option
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 important part of the setting's configuration consists of setting the
an option value can be reseted:: value's option. There are different ways of setting values,
::
>>> cfg.cfgimpl_get_values().reset(gcdummy)
>>> cfg.dummy
False
Setting the values of the options
----------------------------------------
An important part of the setting of the configuration consists of setting the
values of the configuration options. There are different ways of setting values,
the first one is of course the `__setattr__` method the first one is of course the `__setattr__` method
:: ::
@ -110,14 +101,14 @@ adhere to the option description).
Common manipulations 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.config import Config
>>> from tiramisu.option import UnicodeOption, OptionDescription >>> from tiramisu.option import UnicodeOption, OptionDescription
>>> >>> #
>>> var1 = UnicodeOption('var1', 'first variable') >>> var1 = UnicodeOption('var1', 'first variable')
>>> var2 = UnicodeOption('var2', '', u'value') >>> var2 = UnicodeOption('var2', '', u'value')
>>> >>> #
>>> od1 = OptionDescription('od1', 'first OD', [var1, var2]) >>> od1 = OptionDescription('od1', 'first OD', [var1, var2])
>>> rootod = OptionDescription('rootod', '', [od1]) >>> rootod = OptionDescription('rootod', '', [od1])
@ -138,11 +129,10 @@ None
>>> print c.od1.var2 >>> print c.od1.var2
value 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' >>> c.od1.var1 = 'value'
Traceback (most recent call last): Traceback (most recent call last):
[...]
ValueError: invalid value value for option var1 ValueError: invalid value value for option var1
>>> c.od1.var1 = u'value' >>> c.od1.var1 = u'value'
>>> print c.od1.var1 >>> print c.od1.var1
@ -158,18 +148,16 @@ let's come back to the default value
value value
The value is saved in a :class:`~tiramisu.value.Value` object. It is on this 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. (`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 let's retrieve the option `var1` description
@ -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 = Config(rootod)
>>> c.read_write() >>> 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') >>> print c.find(byname='var1')
[<tiramisu.option.UnicodeOption object at 0x7ff1bf7d6ef0>, [<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, {'var5': None, 'var4': None, 'var6': None, 'var1': u'value', 'var3': None,
'var2': 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` two options named `var1`
One can export only interesting parts of a tree of options into a dict, for 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') >>> print c.od1.make_dict(withoption='var1')
{'var1': None, 'var3': None, 'var2': None} {'var1': None, 'var3': None, 'var2': None}
the owners The owners
~~~~~~~~~~~ ~~~~~~~~~~~
.. glossary:: .. glossary::
@ -283,24 +271,36 @@ the owners
Then let's retrieve the owner associated to an option:: Then let's retrieve the owner associated to an option::
>>> print c.getowner('var1') >>> print c.getowner(var1)
default default
>>> c.od1.var1 = u'non' >>> c.od1.var1 = u'no'
>>> print c.getowner('var1') >>> print c.getowner(var1)
user user
>>> del(c.var1) >>> del(c.var1)
>>> print c.getowner('var1') >>> print c.getowner(var1)
default default
the properties 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. A property is an information on an option's state.
Let's create options with properties:: Let's create options with properties::
>>> var1 = UnicodeOption('var1', '', u'value', properties=('hidden',)) >>> var1 = UnicodeOption('var1', '', u'value', properties=('hidden',))
>>> var2 = UnicodeOption('var2', '', properties=('mandatory',)) >>> 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') >>> var4 = UnicodeOption('var4', '', u'value')
>>> od1 = OptionDescription('od1', '', [var1, var2, var3]) >>> od1 = OptionDescription('od1', '', [var1, var2, var3])
>>> od2 = OptionDescription('od2', '', [var4], properties=('hidden',)) >>> od2 = OptionDescription('od2', '', [var4], properties=('hidden',))
@ -314,7 +314,6 @@ with a hidden option::
>>> print c.od1.var1 >>> print c.od1.var1
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var1 tiramisu.error.PropertiesOptionError: trying to access to an option named: var1
with properties ['hidden'] with properties ['hidden']
>>> c.read_only() >>> 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() >>> c.read_only()
>>> print c.od1.var2 >>> print c.od1.var2
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var2 tiramisu.error.PropertiesOptionError: trying to access to an option named: var2
with properties ['mandatory'] with properties ['mandatory']
>>> c.read_write() >>> c.read_write()
@ -348,7 +346,6 @@ Let's try to modify a frozen option::
value value
>>> c.od1.var3 = u'value2' >>> c.od1.var3 = u'value2'
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: cannot change the value for option var3 this option is frozen tiramisu.error.PropertiesOptionError: cannot change the value for option var3 this option is frozen
>>> c.read_only() >>> c.read_only()
>>> print c.od1.var3 >>> 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 Tiramisu allows us to use user defined properties. Let's define and use one in
read/write or read only mode:: read/write or read only mode::
>>> c.cfgimpl_get_settings().append('inconnu') >>> c.cfgimpl_get_settings().append('unknown')
>>> print c.od1.var3 >>> print c.od1.var3
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: tiramisu.error.PropertiesOptionError: trying to access to an option named:
var3 with properties ['inconnu'] var3 with properties ['unknown']
>>> c.cfgimpl_get_settings().remove('inconnu') >>> c.cfgimpl_get_settings().remove('unknown')
>>> print c.od1.var3 >>> print c.od1.var3
value value
Properties can also be defined on an option group, (that is, on an 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:: :term:`option description`) let's hide a group and try to access to it::
>>> c.read_write() >>> c.read_write()
>>> print c.od2.var4 >>> print c.od2.var4
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 tiramisu.error.PropertiesOptionError: trying to access to an option named: od2
with properties ['hidden'] with properties ['hidden']
>>> c.read_only() >>> c.read_only()
@ -387,7 +382,6 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property
['hidden'] ['hidden']
>>> print c.od1.var1 >>> print c.od1.var1
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: tiramisu.error.PropertiesOptionError: trying to access to an option named:
var1 with properties ['hidden'] var1 with properties ['hidden']
>>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('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'] ['hidden']
>>> print c.od1.var1 >>> print c.od1.var1
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: tiramisu.error.PropertiesOptionError: trying to access to an option named:
var1 with properties ['hidden'] var1 with properties ['hidden']
@ -438,11 +431,10 @@ A multi-option's value can be manipulated like a list::
>>> print c.od1.var1 >>> print c.od1.var1
[u'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' >>> c.od1.var1 = u'error'
Traceback (most recent call last): Traceback (most recent call last):
[...]
ValueError: invalid value error for option var1 which must be a list 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'] slave2 = [u'default', u'default']
>>> c.master.slave1 = [u'new1'] >>> c.master.slave1 = [u'new1']
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master
>>> c.master.slave1 = [u'new1', u'new2', u'new3'] >>> c.master.slave1 = [u'new1', u'new2', u'new3']
[...]
tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master 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:: you have to call the `pop` function on the master::
>>> c.master.master = [u'oui'] >>> c.master.master = [u'oui']
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.SlaveError: invalid len for the master: master which has slave1 as slave with greater len tiramisu.error.SlaveError: invalid len for the master: master which has slave1 as slave with greater len
>>> c.master.master.pop(0) >>> c.master.master.pop(0)
u'oui' 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 A :class:`~config.CommonConfig` is a abstract base class. A
:class:`~config.SubConfig` is an just in time created objects that wraps an :class:`~config.SubConfig` is an just in time created objects that wraps an
::class:`~option.OptionDescription`. A SubConfig differs from a Config in the ::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 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 defines the different properties, access rules, vs... There is generally only
::one Config, and many SubConfigs. one Config, and many SubConfigs.

View File

@ -57,7 +57,7 @@ A requirement is a list of dictionaries that have fairly this form::
'transitive':True, 'same_action': True}] 'transitive':True, 'same_action': True}]
Actually a transformation is made to this dictionary during the validation of 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()` 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 method. Take a look at the code to fully understand the exact meaning of the
requirements: requirements:
@ -103,7 +103,6 @@ hidden any more::
['hidden'] ['hidden']
>>> print c.od1.var1 >>> print c.od1.var1
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: tiramisu.error.PropertiesOptionError: trying to access to an option named:
var1 with properties ['hidden'] var1 with properties ['hidden']
>>> c.od1.var2 = u'oui' >>> c.od1.var2 = u'oui'
@ -123,7 +122,6 @@ document.)::
>>> c.od1.var2 = u'non' >>> c.od1.var2 = u'non'
>>> print c.od2.var4 >>> print c.od2.var4
Traceback (most recent call last): Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden'] tiramisu.error.PropertiesOptionError: trying to access to an option named: od2 with properties ['hidden']
>>> c.od1.var2 = u'oui' >>> c.od1.var2 = u'oui'
>>> print c.od2.var4 >>> print c.od2.var4
@ -144,21 +142,20 @@ Requirements can be accumulated for different or identical properties (inverted
or not):: or not)::
>>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2,
'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui',
'action':'hidden'}]) ... 'action':'hidden'}])
>>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2,
'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui', ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'excepted':'oui',
'action':'disabled', 'inverse':True}]) ... 'action':'disabled', 'inverse':True}])
But it is not possible to have inverted requirements on the same property. But it is not possible to have inverted requirements on the same property.
Here is an impossible situation:: Here is an impossible situation::
>>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2, >>> a = UnicodeOption('var3', '', u'value', requires=[{'option':od1.var2,
'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui', ... 'expected':'non', 'action':'hidden'}, {'option':od1.var1, 'expected':'oui',
'hidden', True}]) ... 'hidden', True}])
Traceback (most recent call last): Traceback (most recent call last):
[...]
ValueError: inconsistency in action types for option: var3 action: hidden ValueError: inconsistency in action types for option: var3 action: hidden
Validation upon a whole configuration object 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:: Here is an option wich uses this validator::
>>> var1 = UnicodeOption('var1', '', u'oui', validator=valid_a, validator_args={'letter': 'o'}) >>> var1 = UnicodeOption('var1', '', u'oui', validator=valid_a, validator_args={'letter': 'o'})
>>>
>>> od1 = OptionDescription('od1', '', [var1]) >>> od1 = OptionDescription('od1', '', [var1])
>>>
>>> rootod = OptionDescription('rootod', '', [od1]) >>> rootod = OptionDescription('rootod', '', [od1])
>>>
>>> c = Config(rootod) >>> c = Config(rootod)
>>> c.read_write() >>> c.read_write()
@ -196,11 +190,10 @@ The validation is applied at the modification time::
>>> c.od1.var1 = u'non' >>> c.od1.var1 = u'non'
Traceback (most recent call last): Traceback (most recent call last):
[...]
ValueError: invalid value non for option var1 ValueError: invalid value non for option var1
>>> c.od1.var1 = u'oh non' >>> 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.cfgimpl_get_settings().remove('validator')
>>> c.od1.var1 = u'non' >>> c.od1.var1 = u'non'

View File

@ -23,9 +23,6 @@ option APIs
others others
---------- ----------
.. automodule:: test.test_config_api
:members:
.. automodule:: test.test_mandatory .. automodule:: test.test_mandatory
:members: :members:

View File

@ -14,7 +14,7 @@ introduced...
What is Tiramisu ? 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 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 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 :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) - a namespace (which is `c` here)
- the access of an option's value by the - 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()`. :class:`~tiramisu.option.BoolOption()`.
So, option objects are produced at the entry point `c` and then handed down to So, option objects are produced at the entry point `c` and then handed down to

View File

@ -10,7 +10,7 @@
The tasting of `Tiramisu` The tasting of `Tiramisu`
========================= =========================
.. image:: tiramisu.jpeg .. image:: logo.png
:height: 150px :height: 150px
`Tiramisu` `Tiramisu`
@ -34,6 +34,7 @@ controlling options explanations
getting-started getting-started
config config
option option
storage
status status
consistency consistency
error error

BIN
doc/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -9,7 +9,7 @@ Description of Options
---------------------- ----------------------
All the constructors take a ``name`` and a ``doc`` argument as first All the constructors take a ``name`` and a ``doc`` argument as first
arguments to give the option or option group a name and to document it. arguments to give to the option or option description a name and a description document.
Most constructors take a ``default`` argument that specifies the default Most constructors take a ``default`` argument that specifies the default
value of the option. If this argument is not supplied the default value value of the option. If this argument is not supplied the default value
is assumed to be ``None``. is assumed to be ``None``.
@ -17,7 +17,7 @@ is assumed to be ``None``.
The `Option` base class 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: .. _optioninit:
@ -28,22 +28,41 @@ It's the abstract base class for almost all options (except the symblink).
All option types All option types
------------------ ------------------
BoolOption
~~~~~~~~~~
.. autoclass:: BoolOption .. autoclass:: BoolOption
:private-members: :private-members:
IntOption
~~~~~~~~~
.. autoclass:: IntOption .. autoclass:: IntOption
:private-members: :private-members:
FloatOption
~~~~~~~~~~~
.. autoclass:: FloatOption .. autoclass:: FloatOption
:private-members: :private-members:
StrOption
~~~~~~~~~
.. autoclass:: StrOption .. autoclass:: StrOption
:private-members: :private-members:
UnicodeOption
~~~~~~~~~~~~~
.. autoclass:: UnicodeOption
:private-members:
SymLinkOption
~~~~~~~~~~~~~
.. autoclass:: SymLinkOption .. autoclass:: SymLinkOption
:private-members:
.. automethod:: __init__
``SymLinkOption`` redirects to another configuration option in the ``SymLinkOption`` redirects to another configuration option in the
@ -52,19 +71,41 @@ configuration, that is :
- retrieves the value of the target, - retrieves the value of the target,
- can set the value of the target too - can set the value of the target too
IPOption
~~~~~~~~
.. autoclass:: IPOption .. autoclass:: IPOption
:private-members:
PortOption
~~~~~~~~~~
.. autoclass:: PortOption
:private-members:
NetmaskOption
~~~~~~~~~~~~~
.. autoclass:: NetmaskOption .. autoclass:: NetmaskOption
:private-members:
NetworkOption
~~~~~~~~~~~~~
.. autoclass:: NetworkOption .. autoclass:: NetworkOption
:private-members:
DomainnameOption
~~~~~~~~~~~~~~~~
.. autoclass:: DomainnameOption .. autoclass:: DomainnameOption
:private-members:
ChoiceOption
~~~~~~~~~~~~
.. autoclass:: ChoiceOption .. autoclass:: ChoiceOption
:private-members:
.. automethod:: __init__
.. _optdescr: .. _optdescr:

BIN
doc/storage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

265
doc/storage.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 9.4 KiB

52
doc/storage.txt Normal file
View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,53 +1,63 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.core import setup 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 from os import listdir
import os
import subprocess
def fetch_version(): def fetch_version():
"""Get version from version.in or latest git tag""" """Get version from version.in"""
version_file='version.in' return file('VERSION', 'r').readline().strip()
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
def return_storages(): def return_storages():
"returns all the storage plugins that are living in tiramisu/storage" "returns all the storage plugins that are living in tiramisu/storage"
here = dirname(abspath(__file__)) here = dirname(abspath(__file__))
storages_path = normpath(join(here, 'tiramisu', 'storage')) storages_path = normpath(join(here, 'tiramisu', 'storage'))
dir_content = [ content for content in listdir(storages_path) \ dir_content = [content for content in listdir(storages_path)
if not content =='__pycache__'] if not content == '__pycache__']
storages = filter(isdir, [join(storages_path, content) \ storages = filter(isdir, [join(storages_path, content)
for content in dir_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 return storage_list
packages = ['tiramisu', 'tiramisu.storage'] packages = ['tiramisu', 'tiramisu.storage']
packages.extend(return_storages()) packages.extend(return_storages())
setup( setup(
author='cadoles team', author="Tiramisu's team",
author_email='contact@cadoles.com', author_email='contact@cadoles.com',
name='tiramisu', name='tiramisu',
version=fetch_version(), version=fetch_version(),
description='configuration management tool', description='an options controller tool',
url='http://labs.libre-entreprise.org/projects/tiramisu', 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 packages=packages
) )

View File

@ -4,7 +4,7 @@ from tiramisu import setting
setting.expires_time = 1 setting.expires_time = 1
from tiramisu.option import IntOption, OptionDescription from tiramisu.option import IntOption, OptionDescription
from tiramisu.config import Config from tiramisu.config import Config
from time import sleep from time import sleep, time
def make_description(): def make_description():
@ -20,13 +20,38 @@ def test_cache():
values = c.cfgimpl_get_values() values = c.cfgimpl_get_values()
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u2 c.u2
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
assert 'u2' in values._p_.get_cached('value', c) assert 'u2' in values._p_.get_cached(c)
assert 'u2' in settings._p_.get_cached('property', 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(): def test_cache_reset():
@ -36,44 +61,44 @@ def test_cache_reset():
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
#when change a value #when change a value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u2 = 1 c.u2 = 1
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when remove a value #when remove a value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
del(c.u2) del(c.u2)
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when add/del property #when add/del property
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_get_settings()[od1.u2].append('test') c.cfgimpl_get_settings()[od1.u2].append('test')
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_get_settings()[od1.u2].remove('test') c.cfgimpl_get_settings()[od1.u2].remove('test')
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when enable/disabled property #when enable/disabled property
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_get_settings().append('test') c.cfgimpl_get_settings().append('test')
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_get_settings().remove('test') c.cfgimpl_get_settings().remove('test')
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
def test_cache_reset_multi(): def test_cache_reset_multi():
@ -83,32 +108,32 @@ def test_cache_reset_multi():
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
#when change a value #when change a value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u3 = [1] c.u3 = [1]
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when append value #when append value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u3.append(1) c.u3.append(1)
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when pop value #when pop value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u3.pop(1) c.u3.pop(1)
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
#when remove a value #when remove a value
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
del(c.u3) del(c.u3)
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
def test_reset_cache(): def test_reset_cache():
@ -117,23 +142,25 @@ def test_reset_cache():
values = c.cfgimpl_get_values() values = c.cfgimpl_get_values()
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache() c.cfgimpl_reset_cache()
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
c.u1
sleep(1)
c.u1 c.u1
sleep(1) sleep(1)
c.u2 c.u2
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
assert 'u2' in values._p_.get_cached('value', c) assert 'u2' in values._p_.get_cached(c)
assert 'u2' in settings._p_.get_cached('property', c) assert 'u2' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache() c.cfgimpl_reset_cache()
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
assert 'u2' not in values._p_.get_cached('value', c) assert 'u2' not in values._p_.get_cached(c)
assert 'u2' not in settings._p_.get_cached('property', c) assert 'u2' not in settings._p_.get_cached(c)
def test_reset_cache_subconfig(): def test_reset_cache_subconfig():
@ -142,9 +169,9 @@ def test_reset_cache_subconfig():
c = Config(od2) c = Config(od2)
values = c.cfgimpl_get_values() values = c.cfgimpl_get_values()
c.od1.u1 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() 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(): def test_reset_cache_only_expired():
@ -153,22 +180,60 @@ def test_reset_cache_only_expired():
values = c.cfgimpl_get_values() values = c.cfgimpl_get_values()
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache(True) c.cfgimpl_reset_cache(True)
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
sleep(1)
c.u1
sleep(1) sleep(1)
c.u2 c.u2
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
assert 'u2' in values._p_.get_cached('value', c) assert 'u2' in values._p_.get_cached(c)
assert 'u2' in settings._p_.get_cached('property', c) assert 'u2' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache(True) c.cfgimpl_reset_cache(True)
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)
assert 'u2' in values._p_.get_cached('value', c) assert 'u2' in values._p_.get_cached(c)
assert 'u2' in settings._p_.get_cached('property', 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(c)
assert 'u1' in settings._p_.get_cached(c)
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' 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(): def test_reset_cache_only():
@ -177,14 +242,14 @@ def test_reset_cache_only():
values = c.cfgimpl_get_values() values = c.cfgimpl_get_values()
settings = c.cfgimpl_get_settings() settings = c.cfgimpl_get_settings()
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache(only=('values',)) c.cfgimpl_reset_cache(only=('values',))
assert 'u1' not in values._p_.get_cached('value', c) assert 'u1' not in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.u1 c.u1
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' in settings._p_.get_cached('property', c) assert 'u1' in settings._p_.get_cached(c)
c.cfgimpl_reset_cache(only=('settings',)) c.cfgimpl_reset_cache(only=('settings',))
assert 'u1' in values._p_.get_cached('value', c) assert 'u1' in values._p_.get_cached(c)
assert 'u1' not in settings._p_.get_cached('property', c) assert 'u1' not in settings._p_.get_cached(c)

View File

@ -141,6 +141,7 @@ def test_information_config():
string = 'some informations' string = 'some informations'
config.impl_set_information('info', string) config.impl_set_information('info', string)
assert config.impl_get_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(): def test_config_impl_get_path_by_opt():

View File

@ -19,7 +19,6 @@ def make_description():
boolop = BoolOption('boolop', 'Test boolean option op', default=True) boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Tests', default=False) wantref_option = BoolOption('wantref', 'Tests', default=False)
wantframework_option = BoolOption('wantframework', 'Test', default=False) wantframework_option = BoolOption('wantframework', 'Test', default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption, descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption, 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') #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(): def test_does_not_find_in_config():
descr = make_description() descr = make_description()
conf = Config(descr) conf = Config(descr)

View File

@ -29,6 +29,15 @@ def test_ip_default():
c.a == '88.88.88.88' 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(): def test_network():
a = NetworkOption('a', '') a = NetworkOption('a', '')
od = OptionDescription('od', '', [a]) od = OptionDescription('od', '', [a])

View File

@ -4,19 +4,27 @@ from py.test import raises
from tiramisu.setting import groups from tiramisu.setting import groups
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription StrOption, OptionDescription, SymLinkOption
from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError
def return_val(): def return_val():
return 'val' return 'val'
def return_list(): def return_concat(*args):
return '.'.join(list(args))
def return_list(value=None):
return ['val', 'val'] return ['val', 'val']
def return_value(value): def return_list2(*args):
return list(args)
def return_value(value=None):
return value return value
@ -298,18 +306,73 @@ def test_callback():
def test_callback_value(): def test_callback_value():
val1 = StrOption('val1', "", 'val') val1 = StrOption('val1', "", 'val')
val2 = StrOption('val2', "", callback=return_value, callback_params={'': (('val1', False),)}) val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2]) 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 = Config(maconfig)
cfg.read_write() cfg.read_write()
assert cfg.val1 == 'val' assert cfg.val1 == 'val'
assert cfg.val2 == 'val' assert cfg.val2 == 'val'
assert cfg.val4 == 'val'
cfg.val1 = 'new-val' cfg.val1 = 'new-val'
assert cfg.val1 == 'new-val' assert cfg.val1 == 'new-val'
assert cfg.val2 == 'new-val' assert cfg.val2 == 'new-val'
assert cfg.val4 == 'new-val'
del(cfg.val1) del(cfg.val1)
assert cfg.val1 == 'val' assert cfg.val1 == 'val'
assert cfg.val2 == '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(): def test_callback_list():
@ -336,21 +399,28 @@ def test_callback_multi():
def test_callback_multi_value(): def test_callback_multi_value():
val1 = StrOption('val1', "", ['val'], multi=True) val1 = StrOption('val1', "", ['val'], multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1', False),)}) val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2]) 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 = Config(maconfig)
cfg.read_write() cfg.read_write()
assert cfg.val1 == ['val'] assert cfg.val1 == ['val']
assert cfg.val2 == ['val'] assert cfg.val2 == ['val']
assert cfg.val4 == ['val', 'yes']
cfg.val1 = ['new-val'] cfg.val1 = ['new-val']
assert cfg.val1 == ['new-val'] assert cfg.val1 == ['new-val']
assert cfg.val2 == ['new-val'] assert cfg.val2 == ['new-val']
assert cfg.val4 == ['new-val', 'yes']
cfg.val1.append('new-val2') cfg.val1.append('new-val2')
assert cfg.val1 == ['new-val', 'new-val2'] assert cfg.val1 == ['new-val', 'new-val2']
assert cfg.val2 == ['new-val', 'new-val2'] assert cfg.val2 == ['new-val', 'new-val2']
assert cfg.val4 == ['new-val', 'yes', 'new-val2', 'yes']
del(cfg.val1) del(cfg.val1)
assert cfg.val1 == ['val'] assert cfg.val1 == ['val']
assert cfg.val2 == ['val'] assert cfg.val2 == ['val']
assert cfg.val3 == ['yes']
assert cfg.val4 == ['val', 'yes']
def test_callback_multi_list(): def test_callback_multi_list():
@ -455,41 +525,67 @@ def test_callback_master_and_slaves_slave_list():
def test_callback_master_and_slaves_value(): def test_callback_master_and_slaves_value():
val1 = StrOption('val1', "", multi=True) val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1.val1', False),)}) val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
interface1 = OptionDescription('val1', '', [val1, val2]) 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) interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1]) maconfig = OptionDescription('rootconfig', '', [interface1, val4])
cfg = Config(maconfig) cfg = Config(maconfig)
cfg.read_write() cfg.read_write()
assert cfg.val1.val1 == [] assert cfg.val1.val1 == []
assert cfg.val1.val2 == [] assert cfg.val1.val2 == []
assert cfg.val1.val3 == []
assert cfg.val1.val5 == []
# #
cfg.val1.val1 = ['val1'] cfg.val1.val1 = ['val1']
assert cfg.val1.val1 == ['val1'] assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val1'] assert cfg.val1.val2 == ['val1']
assert cfg.val1.val3 == ['yes']
assert cfg.val1.val5 == ['val10']
# #
cfg.val1.val1.append('val2') cfg.val1.val1.append('val2')
assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['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'] cfg.val1.val1 = ['val1', 'val2', 'val3']
assert cfg.val1.val1 == ['val1', 'val2', 'val3'] assert cfg.val1.val1 == ['val1', 'val2', 'val3']
assert cfg.val1.val2 == ['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) cfg.val1.val1.pop(2)
assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['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.val2 = ['val2', 'val2']
cfg.val1.val3 = ['val2', 'val2']
cfg.val1.val5 = ['val2', 'val2']
assert cfg.val1.val2 == ['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') cfg.val1.val1.append('val3')
assert cfg.val1.val2 == ['val2', 'val2', '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(): def test_callback_hidden():
opt1 = BoolOption('opt1', '') 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',)) od1 = OptionDescription('od1', '', [opt1], properties=('hidden',))
od2 = OptionDescription('od2', '', [opt2]) od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2]) maconfig = OptionDescription('rootconfig', '', [od1, od2])
@ -498,3 +594,84 @@ def test_callback_hidden():
cfg.read_write() cfg.read_write()
raises(PropertiesOptionError, 'cfg.od1.opt1') raises(PropertiesOptionError, 'cfg.od1.opt1')
cfg.od2.opt2 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']

View File

@ -4,7 +4,7 @@ from py.test import raises
from tiramisu.setting import owners, groups from tiramisu.setting import owners, groups
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
OptionDescription SymLinkOption, OptionDescription
def test_consistency_not_equal(): def test_consistency_not_equal():
@ -22,6 +22,16 @@ def test_consistency_not_equal():
c.b = 2 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(): def test_consistency_not_equal_multi():
a = IntOption('a', '', multi=True) a = IntOption('a', '', multi=True)
b = IntOption('b', '', multi=True) b = IntOption('b', '', multi=True)

View File

@ -327,30 +327,29 @@ def test_reset_properties():
cfg = Config(descr) cfg = Config(descr)
setting = cfg.cfgimpl_get_settings() setting = cfg.cfgimpl_get_settings()
option = cfg.cfgimpl_get_description().gc.dummy option = cfg.cfgimpl_get_description().gc.dummy
assert setting._p_.get_properties(cfg) == {} assert setting._p_.get_modified_properties() == {}
setting.append('frozen') 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() setting.reset()
assert setting._p_.get_properties(cfg) == {} assert setting._p_.get_modified_properties() == {}
setting[option].append('test') 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() 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') 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) 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') 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) 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)') raises(ValueError, 'setting.reset(all_properties=True, opt=option)')
a = descr.wantref a = descr.wantref
setting[a].append('test') setting[a].append('test')
setting[a].reset() setting[a].reset()
def test_reset_multiple(): def test_reset_multiple():
descr = make_description() descr = make_description()
cfg = Config(descr) cfg = Config(descr)

View File

@ -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')")

View File

@ -114,7 +114,6 @@ def test_iter_not_group():
raises(TypeError, "list(config.iter_groups(group_type='family'))") raises(TypeError, "list(config.iter_groups(group_type='family'))")
def test_groups_with_master(): def test_groups_with_master():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) 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) 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 == [] 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(): def test_values_with_master_owner():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) 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) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)

View File

@ -88,6 +88,64 @@ def test_multiple_requires():
c.ip_address_service 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(): def test_multiple_requires_inverse():
a = StrOption('activate_service', '') a = StrOption('activate_service', '')
b = IPOption('ip_address_service', '', b = IPOption('ip_address_service', '',
@ -476,3 +534,9 @@ def test_set_item():
c = Config(od) c = Config(od)
c.read_write() c.read_write()
raises(ValueError, 'c.cfgimpl_get_settings()[a] = ("test",)') 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'}])")

View File

@ -4,10 +4,13 @@ from py.test import raises
from tiramisu.config import Config, SubConfig from tiramisu.config import Config, SubConfig
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ 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(): def test_slots_option():
c = ChoiceOption('a', '', ('a',))
raises(AttributeError, "c.x = 1")
c = BoolOption('a', '') c = BoolOption('a', '')
raises(AttributeError, "c.x = 1") raises(AttributeError, "c.x = 1")
c = IntOption('a', '') c = IntOption('a', '')
@ -20,10 +23,96 @@ def test_slots_option():
raises(AttributeError, "c.x = 1") raises(AttributeError, "c.x = 1")
c = UnicodeOption('a', '') c = UnicodeOption('a', '')
raises(AttributeError, "c.x = 1") raises(AttributeError, "c.x = 1")
c = ChoiceOption('a', '', ('a',)) c = IPOption('a', '')
raises(AttributeError, "c.x = 1") raises(AttributeError, "c.x = 1")
c = OptionDescription('a', '', []) c = OptionDescription('a', '', [])
raises(AttributeError, "c.x = 1") 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(): def test_slots_config():

250
test/test_state.py Normal file
View File

@ -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

View File

@ -5,7 +5,7 @@ import autopath
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import BoolOption, OptionDescription from tiramisu.option import BoolOption, OptionDescription
from tiramisu.setting import owners 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(): def test_non_persistent():
@ -18,7 +18,7 @@ def test_list():
b = BoolOption('b', '') b = BoolOption('b', '')
o = OptionDescription('od', '', [b]) o = OptionDescription('od', '', [b])
c = Config(o, session_id='test_non_persistent') 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() assert 'test_non_persistent' in list_sessions()
del(c) del(c)
assert 'test_non_persistent' not in list_sessions() assert 'test_non_persistent' not in list_sessions()
@ -43,7 +43,6 @@ def test_list_sessions_persistent():
# storage is not persistent # storage is not persistent
pass pass
else: else:
from tiramisu.setting import list_sessions
assert 'test_persistent' in list_sessions() assert 'test_persistent' in list_sessions()
@ -66,6 +65,7 @@ def test_create_persistent_retrieve():
o = OptionDescription('od', '', [b]) o = OptionDescription('od', '', [b])
try: try:
c = Config(o, session_id='test_persistent', persistent=True) c = Config(o, session_id='test_persistent', persistent=True)
c.cfgimpl_get_settings().remove('cache')
except ValueError: except ValueError:
# storage is not persistent # storage is not persistent
pass pass
@ -75,10 +75,12 @@ def test_create_persistent_retrieve():
assert c.b is True assert c.b is True
del(c) del(c)
c = Config(o, session_id='test_persistent', persistent=True) c = Config(o, session_id='test_persistent', persistent=True)
c.cfgimpl_get_settings().remove('cache')
assert c.b is True assert c.b is True
assert 'test_persistent' in list_sessions() assert 'test_persistent' in list_sessions()
delete_session('test_persistent') delete_session('test_persistent')
c = Config(o, session_id='test_persistent', persistent=True) c = Config(o, session_id='test_persistent', persistent=True)
c.cfgimpl_get_settings().remove('cache')
assert c.b is None assert c.b is None
delete_session('test_persistent') delete_session('test_persistent')
@ -88,11 +90,13 @@ def test_two_persistent():
o = OptionDescription('od', '', [b]) o = OptionDescription('od', '', [b])
try: try:
c = Config(o, session_id='test_persistent', persistent=True) c = Config(o, session_id='test_persistent', persistent=True)
c.cfgimpl_get_settings().remove('cache')
except ValueError: except ValueError:
# storage is not persistent # storage is not persistent
pass pass
else: else:
c2 = Config(o, session_id='test_persistent', persistent=True) c2 = Config(o, session_id='test_persistent', persistent=True)
c2.cfgimpl_get_settings().remove('cache')
assert c.b is None assert c.b is None
assert c2.b is None assert c2.b is None
c.b = False c.b = False
@ -109,11 +113,13 @@ def test_two_persistent_owner():
o = OptionDescription('od', '', [b]) o = OptionDescription('od', '', [b])
try: try:
c = Config(o, session_id='test_persistent', persistent=True) c = Config(o, session_id='test_persistent', persistent=True)
c.cfgimpl_get_settings().remove('cache')
except ValueError: except ValueError:
# storage is not persistent # storage is not persistent
pass pass
else: else:
c2 = Config(o, session_id='test_persistent', persistent=True) c2 = Config(o, session_id='test_persistent', persistent=True)
c2.cfgimpl_get_settings().remove('cache')
owners.addowner('persistent') owners.addowner('persistent')
assert c.getowner(b) == owners.default assert c.getowner(b) == owners.default
assert c2.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 c.getowner(b) == owners.persistent
assert c2.getowner(b) == owners.persistent assert c2.getowner(b) == owners.persistent
delete_session('test_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')

View File

@ -23,11 +23,9 @@ from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.i18n import _ from tiramisu.i18n import _
# ____________________________________________________________ # ____________________________________________________________
def carry_out_calculation(name,
config, def carry_out_calculation(name, config, callback, callback_params,
callback, index=None, max_len=None):
callback_params,
index=None):
"""a function that carries out a calculation for an option's value """a function that carries out a calculation for an option's value
:param name: the option name (`opt._name`) :param name: the option name (`opt._name`)
@ -40,36 +38,104 @@ def carry_out_calculation(name,
:type callback_params: dict :type callback_params: dict
:param index: if an option is multi, only calculates the nth value :param index: if an option is multi, only calculates the nth value
:type index: int :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 = {} tcparams = {}
one_is_multi = False one_is_multi = False
len_multi = 0 len_multi = 0
for key, values in callback_params.items(): for key, callbacks in callback_params.items():
for value in values: for callbk in callbacks:
if type(value) == tuple: if isinstance(callbk, tuple):
path, check_disabled = value option, force_permissive = callbk
if config is None: # get value
if check_disabled:
continue
raise ConfigError(_('no config specified but needed'))
try: try:
opt_value = config._getattr(path, force_permissive=True) path = config.cfgimpl_get_description().impl_get_path_by_opt(option)
opt = config.unwrap_from_path(path, force_permissive=True) value = config._getattr(path, force_permissive=True)
except PropertiesOptionError as err: except PropertiesOptionError as err:
if check_disabled: if force_permissive:
continue continue
raise ConfigError(_('unable to carry out a calculation, ' raise ConfigError(_('unable to carry out a calculation, '
'option {0} has properties: {1} for: ' 'option {0} has properties: {1} for: '
'{2}').format(path, err.proptype, '{2}').format(option._name, err.proptype,
name)) name))
is_multi = opt.impl_is_multi() is_multi = option.impl_is_multi()
if is_multi: if is_multi:
if opt_value is not None: if value is not None:
len_value = len(opt_value) len_value = len(value)
if len_multi != 0 and len_multi != len_value: if len_multi != 0 and len_multi != len_value:
raise ConfigError(_('unable to carry out a ' raise ConfigError(_('unable to carry out a '
'calculation, option value with' 'calculation, option value with'
@ -77,14 +143,21 @@ def carry_out_calculation(name,
'length for: {0}').format(name)) 'length for: {0}').format(name))
len_multi = len_value len_multi = len_value
one_is_multi = True one_is_multi = True
tcparams.setdefault(key, []).append((opt_value, is_multi)) tcparams.setdefault(key, []).append((value, is_multi))
else: else:
tcparams.setdefault(key, []).append((value, False)) tcparams.setdefault(key, []).append((callbk, False))
if one_is_multi: if one_is_multi:
ret = [] ret = []
if index: if index:
if index < len_multi:
range_ = [index] range_ = [index]
else:
range_ = []
ret = None
else:
if max_len and max_len < len_multi:
range_ = range(max_len)
else: else:
range_ = range(len_multi) range_ = range(len_multi)
for incr in range_: for incr in range_:
@ -97,15 +170,9 @@ def carry_out_calculation(name,
if key == '': if key == '':
params.append(value[incr]) params.append(value[incr])
else: else:
if len(value) > incr:
tcp[key] = value[incr] tcp[key] = value[incr]
else: else:
tcp[key] = ''
else:
if key == '':
params.append(value) params.append(value)
else:
tcp[key] = value
calc = calculate(name, callback, params, tcp) calc = calculate(name, callback, params, tcp)
if index: if index:
ret = calc ret = calc
@ -114,7 +181,6 @@ def carry_out_calculation(name,
ret.extend(calc) ret.extend(calc)
else: else:
ret.append(calc) ret.append(calc)
return ret return ret
else: else:
tcp = {} tcp = {}
@ -130,7 +196,6 @@ def carry_out_calculation(name,
def calculate(name, callback, params, tcparams): def calculate(name, callback, params, tcparams):
# FIXME we don't need the option's name down there.
"""wrapper that launches the 'callback' """wrapper that launches the 'callback'
:param callback: callback name :param callback: callback name

View File

@ -22,14 +22,15 @@
# ____________________________________________________________ # ____________________________________________________________
import weakref import weakref
from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.option import OptionDescription, Option, SymLinkOption, \ from tiramisu.option import OptionDescription, Option, SymLinkOption
BaseInformation from tiramisu.setting import groups, Settings, default_encoding
from tiramisu.setting import groups, Settings, default_encoding, get_storage from tiramisu.storage import get_storages, get_storage, set_storage, \
from tiramisu.value import Values _impl_getstate_setting
from tiramisu.value import Values, Multi
from tiramisu.i18n import _ from tiramisu.i18n import _
class SubConfig(BaseInformation): class SubConfig(object):
"sub configuration management entry" "sub configuration management entry"
__slots__ = ('_impl_context', '_impl_descr', '_impl_path') __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
@ -293,11 +294,12 @@ class SubConfig(BaseInformation):
return True return True
try: try:
value = getattr(self, path) value = getattr(self, path)
if value == byvalue: if isinstance(value, Multi):
return True return byvalue in value
else:
return value == byvalue
except PropertiesOptionError: # a property is a restriction except PropertiesOptionError: # a property is a restriction
# upon the access of the value # upon the access of the value
pass
return False return False
def _filter_by_type(): def _filter_by_type():
@ -323,15 +325,15 @@ class SubConfig(BaseInformation):
continue continue
if not _filter_by_value(): if not _filter_by_value():
continue continue
if not _filter_by_type():
continue
#remove option with propertyerror, ... #remove option with propertyerror, ...
if check_properties: if byvalue is None and check_properties:
try: try:
value = getattr(self, path) value = getattr(self, path)
except PropertiesOptionError: except PropertiesOptionError:
# a property restricts the access of the value # a property restricts the access of the value
continue continue
if not _filter_by_type():
continue
if type_ == 'value': if type_ == 'value':
retval = value retval = value
elif type_ == 'path': elif type_ == 'path':
@ -501,11 +503,27 @@ class CommonConfig(SubConfig):
def cfgimpl_get_meta(self): def cfgimpl_get_meta(self):
return self._impl_meta 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): class Config(CommonConfig):
"main configuration management entry" "main configuration management entry"
__slots__ = ('__weakref__', ) __slots__ = ('__weakref__', '_impl_test')
def __init__(self, descr, session_id=None, persistent=False): def __init__(self, descr, session_id=None, persistent=False):
""" Configuration option management master class """ Configuration option management master class
@ -520,13 +538,49 @@ class Config(CommonConfig):
:param persistent: if persistent, don't delete storage when leaving :param persistent: if persistent, don't delete storage when leaving
:type persistent: `boolean` :type persistent: `boolean`
""" """
storage = get_storage(self, session_id, persistent) settings, values = get_storages(self, session_id, persistent)
self._impl_settings = Settings(self, storage) self._impl_settings = Settings(self, settings)
self._impl_values = Values(self, storage) self._impl_values = Values(self, values)
super(Config, self).__init__(descr, weakref.ref(self)) super(Config, self).__init__(descr, weakref.ref(self))
self._impl_build_all_paths() self._impl_build_all_paths()
self._impl_meta = None 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, def cfgimpl_reset_cache(self,
only_expired=False, only_expired=False,
@ -561,11 +615,10 @@ class Config(CommonConfig):
# child._impl_meta = self # child._impl_meta = self
# self._impl_children = children # self._impl_children = children
# storage = get_storage(self, session_id, persistent) # settings, values = get_storages(self, session_id, persistent)
# self._impl_settings = Settings(self, storage) # self._impl_settings = Settings(self, settings)
# self._impl_values = Values(self, storage) # self._impl_values = Values(self, values)
# self._impl_meta = None # self._impl_meta = None
# self._impl_informations = {}
# def cfgimpl_get_children(self): # def cfgimpl_get_children(self):
# return self._impl_children # return self._impl_children

View File

@ -26,7 +26,7 @@ from copy import copy, deepcopy
from types import FunctionType from types import FunctionType
from IPy import IP from IPy import IP
from tiramisu.error import ConflictError from tiramisu.error import ConflictError, ConfigError
from tiramisu.setting import groups, multitypes from tiramisu.setting import groups, multitypes
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
@ -54,10 +54,78 @@ def valid_name(name):
# #
class BaseInformation(object): class BaseOption(object):
"interface for an option's information attribute" """This abstract base class stands for attribute access
__slots__ = ('_impl_informations',) 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): def impl_set_information(self, key, value):
"""updates the information's attribute """updates the information's attribute
(which is a dictionary) (which is a dictionary)
@ -65,47 +133,206 @@ class BaseInformation(object):
:param key: information's key (ex: "help", "doc" :param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string") :param value: information's value (ex: "the help string")
""" """
try:
self._impl_informations[key] = value self._impl_informations[key] = value
except AttributeError:
raise AttributeError(_('{0} has no attribute '
'impl_set_information').format(
self.__class__.__name__))
def impl_get_information(self, key, default=None): def impl_get_information(self, key, default=None):
"""retrieves one information's item """retrieves one information's item
:param key: the item string (ex: "help") :param key: the item string (ex: "help")
""" """
try:
if key in self._impl_informations: if key in self._impl_informations:
return self._impl_informations[key] return self._impl_informations[key]
elif default is not None: elif default is not None:
return default return default
else: else:
raise ValueError(_("Information's item" raise ValueError(_("information's item not found: {0}").format(
"not found: {0}").format(key)) 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:
_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: except AttributeError:
raise AttributeError(_('{0} has no attribute ' pass
'impl_get_information').format(
self.__class__.__name__)) 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 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. 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', __slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_default_multi', '_default', '_properties', '_callback', '_state_callback', '_callback', '_multitype',
'_multitype', '_master_slaves', '_consistencies', '_master_slaves', '__weakref__')
'_calc_properties', '__weakref__')
_empty = '' _empty = ''
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=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): properties=None):
""" """
:param name: the option's name :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 :param callback: the name of a function. If set, the function's output
is responsible of the option's value is responsible of the option's value
:param callback_params: the callback's parameter :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 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): super(Option, self).__init__(name, doc, requires, properties)
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._multi = multi self._multi = multi
self._consistencies = None
if validator is not None: if validator is not None:
if type(validator) != FunctionType: validate_callback(validator, validator_params, 'validator')
raise TypeError(_("validator must be a function")) self._validator = (validator, validator_params)
if validator_args is None:
validator_args = {}
self._validator = (validator, validator_args)
else: else:
self._validator = None self._validator = None
if not self._multi and default_multi is not None: if not self._multi and default_multi is not None:
@ -161,11 +379,7 @@ class Option(BaseInformation):
"no callback defined" "no callback defined"
" yet for option {0}").format(name)) " yet for option {0}").format(name))
if callback is not None: if callback is not None:
if type(callback) != FunctionType: validate_callback(callback, callback_params, 'callback')
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')
self._callback = (callback, callback_params) self._callback = (callback, callback_params)
else: else:
self._callback = None self._callback = None
@ -176,14 +390,6 @@ class Option(BaseInformation):
self._default_multi = default_multi self._default_multi = default_multi
self.impl_validate(default) self.impl_validate(default)
self._default = 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_): def _launch_consistency(self, func, opt, vals, context, index, opt_):
if context is not None: if context is not None:
@ -240,11 +446,23 @@ class Option(BaseInformation):
def val_validator(val): def val_validator(val):
if self._validator is not None: if self._validator is not None:
callback_params = deepcopy(self._validator[1]) if self._validator[1] is not None:
callback_params.setdefault('', []).insert(0, val) validator_params = deepcopy(self._validator[1])
return carry_out_calculation(self._name, config=context, 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=self._validator[0],
callback_params=callback_params) callback_params=validator_params)
if ret not in [False, True]:
raise ConfigError(_('validator should return a boolean, '
'not {0}').format(ret))
return ret
else: else:
return True return True
@ -345,6 +563,40 @@ class Option(BaseInformation):
"must be different as {2} option" "must be different as {2} option"
"").format(value, self._name, optname)) "").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): class ChoiceOption(Option):
"""represents a choice out of several objects. """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, def __init__(self, name, doc, values, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
callback_params=None, open_values=False, validator=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 :param values: is a list of values the option can possibly take
""" """
@ -376,7 +628,7 @@ class ChoiceOption(Option):
requires=requires, requires=requires,
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_args=validator_args, validator_params=validator_params,
properties=properties) properties=properties)
def impl_get_values(self): def impl_get_values(self):
@ -450,10 +702,11 @@ else:
raise ValueError(_('value must be an unicode')) raise ValueError(_('value must be an unicode'))
class SymLinkOption(object): class SymLinkOption(BaseOption):
__slots__ = ('_name', '_opt') __slots__ = ('_name', '_opt', '_state_opt')
_opt_type = 'symlink' _opt_type = 'symlink'
_consistencies = None #not return _opt consistencies
_consistencies = {}
def __init__(self, name, opt): def __init__(self, name, opt):
self._name = name self._name = name
@ -462,24 +715,41 @@ class SymLinkOption(object):
'must be an option ' 'must be an option '
'for symlink {0}').format(name)) 'for symlink {0}').format(name))
self._opt = opt self._opt = opt
self._readonly = True
def __getattr__(self, name): def __getattr__(self, name):
if name in ('_name', '_opt', '_consistencies'): if name in ('_name', '_opt', '_opt_type', '_readonly'):
return object.__getattr__(self, name) return object.__getattr__(self, name)
else: else:
return getattr(self._opt, name) 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): class IPOption(Option):
"represents the choice of an ip" "represents the choice of an ip"
__slots__ = ('_only_private',) __slots__ = ('_only_private', '_allow_reserved')
_opt_type = 'ip' _opt_type = 'ip'
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=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, only_private=False): properties=None, only_private=False, allow_reserved=False):
self._only_private = only_private self._only_private = only_private
self._allow_reserved = allow_reserved
super(IPOption, self).__init__(name, doc, default=default, super(IPOption, self).__init__(name, doc, default=default,
default_multi=default_multi, default_multi=default_multi,
callback=callback, callback=callback,
@ -487,12 +757,12 @@ class IPOption(Option):
requires=requires, requires=requires,
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_args=validator_args, validator_params=validator_params,
properties=properties) properties=properties)
def _validate(self, value): def _validate(self, value):
ip = IP('{0}/32'.format(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")) raise ValueError(_("IP mustn't not be in reserved class"))
if self._only_private and not ip.iptype() == 'PRIVATE': if self._only_private and not ip.iptype() == 'PRIVATE':
raise ValueError(_("IP must be in private class")) 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, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=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, properties=None, allow_range=False, allow_zero=False,
allow_wellknown=True, allow_registred=True, allow_wellknown=True, allow_registred=True,
allow_private=False): allow_private=False):
@ -547,7 +817,7 @@ class PortOption(Option):
requires=requires, requires=requires,
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_args=validator_args, validator_params=validator_params,
properties=properties) properties=properties)
def _validate(self, value): def _validate(self, value):
@ -575,7 +845,7 @@ class NetworkOption(Option):
def _validate(self, value): def _validate(self, value):
ip = IP(value) ip = IP(value)
if ip.iptype() == 'RESERVED': 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): class NetmaskOption(Option):
@ -632,7 +902,7 @@ class DomainnameOption(Option):
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=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'): properties=None, allow_ip=False, type_='domainname'):
#netbios: for MS domain #netbios: for MS domain
#hostname: to identify the device #hostname: to identify the device
@ -651,7 +921,7 @@ class DomainnameOption(Option):
requires=requires, requires=requires,
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_args=validator_args, validator_params=validator_params,
properties=properties) properties=properties)
def _validate(self, value): def _validate(self, value):
@ -674,7 +944,7 @@ class DomainnameOption(Option):
raise ValueError(_("invalid value for {0}, must have dot" raise ValueError(_("invalid value for {0}, must have dot"
"").format(self._name)) "").format(self._name))
if len(value) > length: 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)) " {0} (max {1})").format(self._name, length))
if len(value) == 1: if len(value) == 1:
raise ValueError(_("invalid domainname's length for {0} (min 2)" raise ValueError(_("invalid domainname's length for {0} (min 2)"
@ -684,25 +954,23 @@ class DomainnameOption(Option):
raise ValueError(_('invalid domainname')) raise ValueError(_('invalid domainname'))
class OptionDescription(BaseInformation): class OptionDescription(BaseOption):
"""Config's schema (organisation, group) and container of Options """Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`. The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
""" """
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type', __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_properties', '_children', '_consistencies', '_state_group_type', '_properties', '_children',
'_calc_properties', '__weakref__') '_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): def __init__(self, name, doc, children, requires=None, properties=None):
""" """
:param children: a list of options (including optiondescriptions) :param children: a list of options (including optiondescriptions)
""" """
if not valid_name(name): super(OptionDescription, self).__init__(name, doc, requires, properties)
raise ValueError(_("invalid name: "
" {0} for optiondescription").format(name))
self._name = name
self._impl_informations = {}
self.impl_set_information('doc', doc)
child_names = [child._name for child in children] child_names = [child._name for child in children]
#better performance like this #better performance like this
valid_child = copy(child_names) valid_child = copy(child_names)
@ -714,16 +982,7 @@ class OptionDescription(BaseInformation):
'{0}').format(child)) '{0}').format(child))
old = child old = child
self._children = (tuple(child_names), tuple(children)) self._children = (tuple(child_names), tuple(children))
self._calc_properties, self._requires = validate_requires_arg(requires, self._name)
self._cache_paths = None 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 # the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default self._group_type = groups.default
@ -731,6 +990,8 @@ class OptionDescription(BaseInformation):
return self.impl_get_information('doc') return self.impl_get_information('doc')
def __getattr__(self, name): def __getattr__(self, name):
if name in self.__slots__:
return object.__getattribute__(self, name)
try: try:
return self._children[1][self._children[0].index(name)] return self._children[1][self._children[0].index(name)]
except ValueError: except ValueError:
@ -767,12 +1028,15 @@ class OptionDescription(BaseInformation):
cache_path=None, cache_path=None,
cache_option=None, cache_option=None,
_currpath=None, _currpath=None,
_consistencies=None): _consistencies=None,
force_no_consistencies=False):
if _currpath is None and self._cache_paths is not None: if _currpath is None and self._cache_paths is not None:
# cache already set
return return
if _currpath is None: if _currpath is None:
save = True save = True
_currpath = [] _currpath = []
if not force_no_consistencies:
_consistencies = {} _consistencies = {}
else: else:
save = False save = False
@ -781,15 +1045,16 @@ class OptionDescription(BaseInformation):
cache_option = [] cache_option = []
for option in self.impl_getchildren(): for option in self.impl_getchildren():
attr = option._name attr = option._name
if attr.startswith('_cfgimpl'):
continue
if option in cache_option: if option in cache_option:
raise ConflictError(_('duplicate option: {0}').format(option)) raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option) cache_option.append(option)
if not force_no_consistencies:
option._readonly = True
cache_path.append(str('.'.join(_currpath + [attr]))) cache_path.append(str('.'.join(_currpath + [attr])))
if not isinstance(option, OptionDescription): 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: for consistency in option._consistencies:
func, opt = consistency func, opt = consistency
opts = (option, opt) opts = (option, opt)
@ -802,11 +1067,14 @@ class OptionDescription(BaseInformation):
option.impl_build_cache(cache_path, option.impl_build_cache(cache_path,
cache_option, cache_option,
_currpath, _currpath,
_consistencies) _consistencies,
force_no_consistencies)
_currpath.pop() _currpath.pop()
if save: if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._cache_paths = (tuple(cache_option), tuple(cache_path))
if not force_no_consistencies:
self._consistencies = _consistencies self._consistencies = _consistencies
self._readonly = True
def impl_get_opt_by_path(self, path): def impl_get_opt_by_path(self, path):
try: try:
@ -870,7 +1138,7 @@ class OptionDescription(BaseInformation):
raise ValueError(_("no child has same nom has master group" raise ValueError(_("no child has same nom has master group"
" for: {0}").format(self._name)) " for: {0}").format(self._name))
else: else:
raise ValueError(_('group_type : {0}' raise ValueError(_('group_type: {0}'
' not allowed').format(group_type)) ' not allowed').format(group_type))
def impl_get_group_type(self): def impl_get_group_type(self):
@ -891,6 +1159,56 @@ class OptionDescription(BaseInformation):
return False return False
return True 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): def validate_requires_arg(requires, name):
"""check malformed requirements """check malformed requirements
@ -906,6 +1224,8 @@ def validate_requires_arg(requires, name):
ret_requires = {} ret_requires = {}
config_action = {} config_action = {}
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires: for require in requires:
if not type(require) == dict: if not type(require) == dict:
raise ValueError(_("malformed requirements type for option:" raise ValueError(_("malformed requirements type for option:"
@ -919,6 +1239,7 @@ def validate_requires_arg(requires, name):
'{2}'.format(name, '{2}'.format(name,
unknown_keys, unknown_keys,
valid_keys)) valid_keys))
# prepare all attributes
try: try:
option = require['option'] option = require['option']
expected = require['expected'] expected = require['expected']
@ -967,12 +1288,43 @@ def validate_requires_arg(requires, name):
inverse, transitive, same_action) inverse, transitive, same_action)
else: else:
ret_requires[action][option][1].append(expected) ret_requires[action][option][1].append(expected)
# transform dict to tuple
ret = [] ret = []
for opt_requires in ret_requires.values(): for opt_requires in ret_requires.values():
ret_action = [] ret_action = []
for require in opt_requires.values(): for require in opt_requires.values():
req = (require[0], tuple(require[1]), require[2], require[3], ret_action.append((require[0], tuple(require[1]), require[2],
require[4], require[5]) require[3], require[4], require[5]))
ret_action.append(req)
ret.append(tuple(ret_action)) ret.append(tuple(ret_action))
return frozenset(config_action.keys()), tuple(ret) 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)))

View File

@ -24,43 +24,92 @@ from time import time
from copy import copy from copy import copy
import weakref import weakref
from tiramisu.error import (RequirementError, PropertiesOptionError, from tiramisu.error import (RequirementError, PropertiesOptionError,
ConstError, ConfigError) ConstError)
from tiramisu.i18n import _ from tiramisu.i18n import _
"Default encoding for display a Config if raise UnicodeEncodeError"
default_encoding = 'utf-8' 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 expires_time = 5
ro_remove = ('permissive', 'hidden') """List of default properties (you can add new one if needed).
ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen',
'mandatory') For common properties and personalise properties, if a propery is set for
rw_remove = ('permissive', 'everything_frozen', 'mandatory') an Option and for the Config together, Setting raise a PropertiesOptionError
rw_append = ('frozen', 'disabled', 'validator', 'hidden')
default_properties = ('expire', 'validator') * 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: * Special property:
default_storage = 'dictionary'
storage_type = None
def set_storage(self, name): permissive
if self.storage_type is not None: option with 'permissive' cannot raise PropertiesOptionError for properties
raise ConfigError(_('storage_type is already set, cannot rebind it')) set in permissive
self.storage_type = name config with 'permissive', whole option in this config cannot raise
PropertiesOptionError for properties set in permissive
def get_storage(self): * Special Config properties:
if self.storage_type is None:
self.storage_type = self.default_storage cache
storage = self.storage_type if set, enable cache settings and values
return 'tiramisu.storage.{0}.storage'.format(
storage) 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)
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'])
storage_type = StorageType() # ____________________________________________________________
class _NameSpace(object):
class _NameSpace:
"""convenient class that emulates a module """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): def __setattr__(self, name, value):
if name in self.__dict__: if name in self.__dict__:
@ -73,7 +122,6 @@ class _NameSpace:
raise ValueError(name) raise ValueError(name)
# ____________________________________________________________
class GroupModule(_NameSpace): class GroupModule(_NameSpace):
"emulates a module to manage unique group (OptionDescription) names" "emulates a module to manage unique group (OptionDescription) names"
class GroupType(str): class GroupType(str):
@ -91,21 +139,8 @@ class GroupModule(_NameSpace):
*master* means : groups that have the 'master' attribute set *master* means : groups that have the 'master' attribute set
""" """
pass 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): class OwnerModule(_NameSpace):
"""emulates a module to manage unique owner names. """emulates a module to manage unique owner names.
@ -119,28 +154,6 @@ class OwnerModule(_NameSpace):
class DefaultOwner(Owner): class DefaultOwner(Owner):
"""groups that are default (typically 'default')""" """groups that are default (typically 'default')"""
pass pass
# setting.owners (emulates a module)
owners = OwnerModule()
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
"""
setattr(owners, 'default', owners.DefaultOwner('default'))
setattr(owners, 'user', owners.Owner('user'))
def addowner(name):
"""
:param name: the name of the new owner
"""
setattr(owners, name, owners.Owner(name))
setattr(owners, 'addowner', addowner)
# names are in the module now
populate_owners()
class MultiTypeModule(_NameSpace): class MultiTypeModule(_NameSpace):
@ -157,18 +170,79 @@ class MultiTypeModule(_NameSpace):
class SlaveMultiType(MultiType): class SlaveMultiType(MultiType):
pass pass
multitypes = MultiTypeModule()
# ____________________________________________________________
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
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'))
def addowner(name):
"""
:param name: the name of the new owner
"""
setattr(owners, name, owners.Owner(name))
setattr(owners, 'addowner', addowner)
def populate_multitypes(): def populate_multitypes():
"populates the master/slave namespace" """all multi option should have a type, this type is automaticly set do
not touch this
default
default's multi option set if not master or slave
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
"""
setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
setattr(multitypes, 'master', multitypes.MasterMultiType('master')) setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave')) 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() populate_multitypes()
# ____________________________________________________________
class Property(object): class Property(object):
"a property is responsible of the option's value access rules" "a property is responsible of the option's value access rules"
__slots__ = ('_setting', '_properties', '_opt', '_path') __slots__ = ('_setting', '_properties', '_opt', '_path')
@ -191,7 +265,8 @@ class Property(object):
def remove(self, propname): def remove(self, propname):
if propname in self._properties: if propname in self._properties:
self._properties.remove(propname) 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): def reset(self):
self._setting.reset(_path=self._path) self._setting.reset(_path=self._path)
@ -203,39 +278,6 @@ class Property(object):
return str(list(self._properties)) 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): class Settings(object):
"``Config()``'s configuration options" "``Config()``'s configuration options"
@ -254,9 +296,7 @@ class Settings(object):
# generic owner # generic owner
self._owner = owners.user self._owner = owners.user
self.context = weakref.ref(context) self.context = weakref.ref(context)
import_lib = 'tiramisu.storage.{0}.setting'.format(storage.storage) self._p_ = storage
self._p_ = __import__(import_lib, globals(), locals(), ['Settings']
).Settings(storage)
#____________________________________________________________ #____________________________________________________________
# properties methods # properties methods
@ -268,7 +308,7 @@ class Settings(object):
return str(list(self._getproperties())) return str(list(self._getproperties()))
def __getitem__(self, opt): def __getitem__(self, opt):
path = self._get_opt_path(opt) path = self._get_path_by_opt(opt)
return self._getitem(opt, path) return self._getitem(opt, path)
def _getitem(self, opt, path): def _getitem(self, opt, path):
@ -285,7 +325,7 @@ class Settings(object):
self._p_.reset_all_propertives() self._p_.reset_all_propertives()
else: else:
if opt is not None and _path is None: 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._p_.reset_properties(_path)
self.context().cfgimpl_reset_cache() self.context().cfgimpl_reset_cache()
@ -297,18 +337,21 @@ class Settings(object):
raise ValueError(_('if opt is not None, path should not be' raise ValueError(_('if opt is not None, path should not be'
' None in _getproperties')) ' None in _getproperties'))
ntime = None ntime = None
if self._p_.hascache('property', path): if 'cache' in self and self._p_.hascache(path):
ntime = time() if 'expire' in self:
is_cached, props = self._p_.getcache('property', path, ntime) ntime = int(time())
is_cached, props = self._p_.getcache(path, ntime)
if is_cached: if is_cached:
return props return props
props = self._p_.getproperties(path, opt._properties) props = self._p_.getproperties(path, opt._properties)
if is_apply_req: if is_apply_req:
props |= self.apply_requires(opt, path) props |= self.apply_requires(opt, path)
if 'cache' in self:
if 'expire' in self: if 'expire' in self:
if ntime is None: if ntime is None:
ntime = time() ntime = int(time())
self._p_.setcache('property', path, props, ntime + expires_time) ntime = ntime + expires_time
self._p_.setcache(path, props, ntime)
return props return props
def append(self, propname): def append(self, propname):
@ -342,13 +385,17 @@ class Settings(object):
#____________________________________________________________ #____________________________________________________________
def validate_properties(self, opt_or_descr, is_descr, is_write, path, def validate_properties(self, opt_or_descr, is_descr, is_write, path,
value=None, force_permissive=False, value=None, force_permissive=False,
force_properties=None): force_properties=None, force_permissives=None):
""" """
validation upon the properties related to `opt_or_descr` validation upon the properties related to `opt_or_descr`
:param opt_or_descr: an option or an option description object :param opt_or_descr: an option or an option description object
:param force_permissive: behaves as if the permissive property :param force_permissive: behaves as if the permissive property
was present 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, :param is_descr: we have to know if we are in an option description,
just because the mandatory property just because the mandatory property
doesn't exist here doesn't exist here
@ -365,6 +412,8 @@ class Settings(object):
self_properties = copy(self._getproperties()) self_properties = copy(self._getproperties())
if force_permissive is True or 'permissive' in self_properties: if force_permissive is True or 'permissive' in self_properties:
properties -= self._p_.getpermissive() properties -= self._p_.getpermissive()
if force_permissives is not None:
properties -= force_permissives
# global properties # global properties
if force_properties is not None: if force_properties is not None:
@ -410,7 +459,7 @@ class Settings(object):
instead of passing a :class:`tiramisu.option.Option()` object. instead of passing a :class:`tiramisu.option.Option()` object.
""" """
if opt is not None and path is None: 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): if not isinstance(permissive, tuple):
raise TypeError(_('permissive must be a tuple')) raise TypeError(_('permissive must be a tuple'))
self._p_.setpermissive(path, permissive) self._p_.setpermissive(path, permissive)
@ -433,18 +482,23 @@ class Settings(object):
self.append(prop) self.append(prop)
def read_only(self): def read_only(self):
"convenience method to freeze, hidde and disable" "convenience method to freeze, hide and disable"
self._read(ro_remove, ro_append) self._read(ro_remove, ro_append)
def read_write(self): def read_write(self):
"convenience method to freeze, hidde and disable" "convenience method to freeze, hide and disable"
self._read(rw_remove, rw_append) self._read(rw_remove, rw_append)
def reset_cache(self, only_expired): 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: if only_expired:
self._p_.reset_expired_cache('property', time()) self._p_.reset_expired_cache(int(time()))
else: else:
self._p_.reset_all_cache('property') self._p_.reset_all_cache()
def apply_requires(self, opt, path): def apply_requires(self, opt, path):
"""carries out the jit (just in time) requirements between options """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: 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 - **action** is the (property) action to be accomplished if the target
happens to have the expected value option happens to have the expected value
- if **inverse** is `True` and if the target option's value does not - 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 apply, then the property action must be removed from the option's
@ -498,7 +553,7 @@ class Settings(object):
for require in requires: for require in requires:
option, expected, action, inverse, \ option, expected, action, inverse, \
transitive, same_action = require transitive, same_action = require
reqpath = self._get_opt_path(option) reqpath = self._get_path_by_opt(option)
if reqpath == path or reqpath.startswith(path + '.'): if reqpath == path or reqpath.startswith(path + '.'):
raise RequirementError(_("malformed requirements " raise RequirementError(_("malformed requirements "
"imbrication detected for option:" "imbrication detected for option:"
@ -529,5 +584,30 @@ class Settings(object):
break 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) 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'])

View 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)

View 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)

View File

@ -17,7 +17,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 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): class Settings(Cache):
@ -50,12 +50,21 @@ class Settings(Cache):
except KeyError: except KeyError:
pass pass
def get_properties(self, context):
return self._properties
# permissive # permissive
def setpermissive(self, path, permissive): def setpermissive(self, path, permissive):
self._permissives[path] = frozenset(permissive) self._permissives[path] = frozenset(permissive)
def getpermissive(self, path=None): def getpermissive(self, path=None):
return self._permissives.get(path, frozenset()) 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

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"default plugin for cache: set it in a simple dictionary"
# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -19,9 +18,12 @@
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.error import ConfigError from tiramisu.error import ConfigError
from ..util import SerializeObject
class Setting(object): class Setting(SerializeObject):
"""Dictionary storage has no particular setting.
"""
pass pass
@ -38,15 +40,18 @@ def delete_session(session_id):
class Storage(object): class Storage(object):
__slots__ = ('session_id',) __slots__ = ('session_id', 'persistent')
storage = 'dictionary' storage = 'dictionary'
#if object could be serializable
serializable = True
def __init__(self, session_id, persistent): def __init__(self, session_id, persistent, test=False):
if session_id in _list_sessions: if not test and session_id in _list_sessions:
raise ValueError(_('session already used')) raise ValueError(_('session already used'))
if persistent: if persistent:
raise ValueError(_('a dictionary cannot be persistent')) raise ValueError(_('a dictionary cannot be persistent'))
self.session_id = session_id self.session_id = session_id
self.persistent = persistent
_list_sessions.append(self.session_id) _list_sessions.append(self.session_id)
def __del__(self): def __del__(self):
@ -54,45 +59,3 @@ class Storage(object):
_list_sessions.remove(self.session_id) _list_sessions.remove(self.session_id)
except AttributeError: except AttributeError:
pass 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

View File

@ -18,16 +18,17 @@
# #
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.storage.dictionary.storage import Cache from ..util import Cache
class Values(Cache): class Values(Cache):
__slots__ = ('_values', '__weakref__') __slots__ = ('_values', '_informations', '__weakref__')
def __init__(self, storage): def __init__(self, storage):
"""init plugin means create values storage """init plugin means create values storage
""" """
self._values = {} self._values = {}
self._informations = {}
# should init cache too # should init cache too
super(Values, self).__init__(storage) super(Values, self).__init__(storage)
@ -72,3 +73,22 @@ class Values(Cache):
return: owner object return: owner object
""" """
return self._values.get(path, (default, None))[0] 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")

View 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)

View File

@ -17,10 +17,10 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 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() __slots__ = tuple()
def __init__(self, storage): def __init__(self, storage):
@ -29,22 +29,22 @@ class Settings(Cache):
permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path text ' permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path text '
permissives_table += 'primary key, permissives text)' permissives_table += 'primary key, permissives text)'
# should init cache too # should init cache too
super(Settings, self).__init__('property', storage) super(Settings, self).__init__(storage)
self.storage.execute(settings_table, commit=False) self._storage.execute(settings_table, commit=False)
self.storage.execute(permissives_table) self._storage.execute(permissives_table)
# propertives # propertives
def setproperties(self, path, properties): def setproperties(self, path, properties):
path = self._sqlite_encode_path(path) path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE path = ?", (path,), self._storage.execute("DELETE FROM property WHERE path = ?", (path,),
False) False)
self.storage.execute("INSERT INTO property(path, properties) VALUES " self._storage.execute("INSERT INTO property(path, properties) VALUES "
"(?, ?)", (path, "(?, ?)", (path,
self._sqlite_encode(properties))) self._sqlite_encode(properties)))
def getproperties(self, path, default_properties): def getproperties(self, path, default_properties):
path = self._sqlite_encode_path(path) path = self._sqlite_encode_path(path)
value = self.storage.select("SELECT properties FROM property WHERE " value = self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) "path = ?", (path,))
if value is None: if value is None:
return set(default_properties) return set(default_properties)
@ -53,42 +53,53 @@ class Settings(Cache):
def hasproperties(self, path): def hasproperties(self, path):
path = self._sqlite_encode_path(path) path = self._sqlite_encode_path(path)
return self.storage.select("SELECT properties FROM property WHERE " return self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) is not None "path = ?", (path,)) is not None
def reset_all_propertives(self): def reset_all_propertives(self):
self.storage.execute("DELETE FROM property") self._storage.execute("DELETE FROM property")
def reset_properties(self, path): def reset_properties(self, path):
path = self._sqlite_encode_path(path) path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE 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
# permissive # permissive
def setpermissive(self, path, permissive): def setpermissive(self, path, permissive):
path = self._sqlite_encode_path(path) path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM permissive WHERE path = ?", (path,), self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
False) False)
self.storage.execute("INSERT INTO permissive(path, permissives) " self._storage.execute("INSERT INTO permissive(path, permissives) "
"VALUES (?, ?)", (path, "VALUES (?, ?)", (path,
self._sqlite_encode(permissive) self._sqlite_encode(permissive)
)) ))
def getpermissive(self, path='_none'): def getpermissive(self, path='_none'):
permissives = self.storage.select("SELECT permissives FROM " permissives = self._storage.select("SELECT permissives FROM "
"permissive WHERE path = ?", "permissive WHERE path = ?",
(path,)) (path,))
if permissives is None: if permissives is None:
return frozenset() return frozenset()
else: else:
return frozenset(self._sqlite_decode(permissives[0])) 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

View File

@ -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)

View File

@ -18,14 +18,17 @@
# #
# ____________________________________________________________ # ____________________________________________________________
from pickle import dumps, loads
from os import unlink from os import unlink
from os.path import basename, splitext, join from os.path import basename, splitext, join
import sqlite3 import sqlite3
from glob import glob 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' extension = 'db'
dir_database = '/tmp' dir_database = '/tmp'
@ -50,13 +53,17 @@ def delete_session(session_id):
class Storage(object): class Storage(object):
__slots__ = ('_conn', '_cursor', 'persistent', '_session_id') __slots__ = ('_conn', '_cursor', 'persistent', 'session_id', 'serializable')
storage = 'sqlite3' storage = 'sqlite3'
def __init__(self, session_id, persistent): def __init__(self, session_id, persistent, test=False):
self.persistent = persistent self.persistent = persistent
self._session_id = session_id if self.persistent:
self._conn = sqlite3.connect(_gen_filename(self._session_id)) 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._conn.text_factory = str
self._cursor = self._conn.cursor() self._cursor = self._conn.cursor()
@ -78,82 +85,4 @@ class Storage(object):
self._cursor.close() self._cursor.close()
self._conn.close() self._conn.close()
if not self.persistent: if not self.persistent:
delete_session(self._session_id) 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

View File

@ -18,22 +18,25 @@
# #
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.storage.sqlite3.storage import Cache from .sqlite3db import Sqlite3DB
from tiramisu.setting import owners from tiramisu.setting import owners
class Values(Cache): class Values(Sqlite3DB):
__slots__ = ('__weakref__',) __slots__ = ('__weakref__',)
def __init__(self, storage): def __init__(self, storage):
"""init plugin means create values storage """init plugin means create values storage
""" """
# should init cache too # 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 = 'CREATE TABLE IF NOT EXISTS value(path text primary '
values_table += 'key, value text, owner text)' values_table += 'key, value text, owner text)'
self.storage.execute(values_table) self._storage.execute(values_table, commit=False)
for owner in self.storage.select("SELECT DISTINCT owner FROM value", tuple(), 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: try:
getattr(owners, owner[0]) getattr(owners, owner[0])
except AttributeError: except AttributeError:
@ -41,7 +44,7 @@ class Values(Cache):
# sqlite # sqlite
def _sqlite_select(self, path): 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,)) (path,))
# value # value
@ -51,7 +54,7 @@ class Values(Cache):
""" """
self.resetvalue(path) self.resetvalue(path)
path = self._sqlite_encode_path(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), "(?, ?, ?)", (path, self._sqlite_encode(value),
str(owner))) str(owner)))
@ -73,14 +76,14 @@ class Values(Cache):
"""remove value means delete value in storage """remove value means delete value in storage
""" """
path = self._sqlite_encode_path(path) 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): def get_modified_values(self):
"""return all values in a dictionary """return all values in a dictionary
example: {option1: (owner, 'value1'), option2: (owner, 'value2')} example: {option1: (owner, 'value1'), option2: (owner, 'value2')}
""" """
ret = {} 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): only_one=False):
path = self._sqlite_decode_path(path) path = self._sqlite_decode_path(path)
owner = getattr(owners, owner) owner = getattr(owners, owner)
@ -94,7 +97,7 @@ class Values(Cache):
"""change owner for an option """change owner for an option
""" """
path = self._sqlite_encode_path(path) 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)) (str(owner), path))
def getowner(self, path, default): def getowner(self, path, default):
@ -102,7 +105,7 @@ class Values(Cache):
return: owner object return: owner object
""" """
path = self._sqlite_encode_path(path) 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,)) (path,))
if owner is None: if owner is None:
return default return default
@ -114,3 +117,27 @@ class Values(Cache):
except AttributeError: except AttributeError:
owners.addowner(owner) owners.addowner(owner)
return getattr(owners, 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])

110
tiramisu/storage/util.py Normal file
View File

@ -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

View File

@ -44,9 +44,7 @@ class Values(object):
""" """
self.context = weakref.ref(context) self.context = weakref.ref(context)
# the storage type is dictionary or sqlite3 # the storage type is dictionary or sqlite3
import_lib = 'tiramisu.storage.{0}.value'.format(storage.storage) self._p_ = storage
self._p_ = __import__(import_lib, globals(), locals(), ['Values'],
).Values(storage)
def _getdefault(self, opt): def _getdefault(self, opt):
""" """
@ -126,7 +124,7 @@ class Values(object):
return True return True
return False 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 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(), return carry_out_calculation(opt._name, config=self.context(),
callback=callback, callback=callback,
callback_params=callback_params, callback_params=callback_params,
index=index) index=index, max_len=max_len)
def __getitem__(self, opt): def __getitem__(self, opt):
"enables us to use the pythonic dictionary-like access to values" "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, def getitem(self, opt, path=None, validate=True, force_permissive=False,
force_properties=None, validate_properties=True): force_properties=None, validate_properties=True):
ntime = None
if path is None: if path is None:
path = self._get_opt_path(opt) path = self._get_opt_path(opt)
if self._p_.hascache('value', path): ntime = None
ntime = time() setting = self.context().cfgimpl_get_settings()
is_cached, value = self._p_.getcache('value', path, ntime) 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 is_cached:
if opt.impl_is_multi() and not isinstance(value, Multi): if opt.impl_is_multi() and not isinstance(value, Multi):
#load value so don't need to validate if is not a Multi #load value so don't need to validate if is not a Multi
value = Multi(value, self.context, opt, path, validate=False) value = Multi(value, self.context, opt, path, validate=False)
return value return value
val = self._getitem(opt, path, validate, force_permissive, force_properties, val = self._getitem(opt, path, validate, force_permissive,
validate_properties) force_properties, validate_properties)
if 'expire' in self.context().cfgimpl_get_settings() and validate and \ if 'cache' in setting and validate and validate_properties and \
validate_properties and force_permissive is False and \ force_permissive is False and force_properties is None:
force_properties is None: if 'expire' in setting:
if ntime is None: if ntime is None:
ntime = time() ntime = int(time())
self._p_.setcache('value', path, val, ntime + expires_time) ntime = ntime + expires_time
self._p_.setcache(path, val, ntime)
return val return val
@ -176,11 +177,20 @@ class Values(object):
# options with callbacks # options with callbacks
setting = self.context().cfgimpl_get_settings() setting = self.context().cfgimpl_get_settings()
is_frozen = 'frozen' in setting[opt] 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 # if value is callback and is not set
# or frozen with force_default_on_freeze # or frozen with force_default_on_freeze
if opt.impl_has_callback() and ( if opt.impl_has_callback() and (
self._is_default_owner(path) or self._is_default_owner(path) or
(is_frozen and 'force_default_on_freeze' in setting[opt])): (is_frozen and 'force_default_on_freeze' in setting[opt])):
lenmaster = None
no_value_slave = False no_value_slave = False
if (opt.impl_is_multi() and if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave): opt.impl_get_multitype() == multitypes.slave):
@ -192,11 +202,21 @@ class Values(object):
no_value_slave = True no_value_slave = True
if not no_value_slave: if not no_value_slave:
value = self._getcallback_value(opt) 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 if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave): opt.impl_get_multitype() == multitypes.slave):
if not isinstance(value, list): if not isinstance(value, list):
value = [value for i in range(lenmaster)] value = [value for i in range(lenmaster)]
if config_error is None:
if opt.impl_is_multi(): if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate) value = Multi(value, self.context, opt, path, validate)
# suppress value if already set # suppress value if already set
@ -208,15 +228,18 @@ class Values(object):
value = Multi(value, self.context, opt, path, validate) value = Multi(value, self.context, opt, path, validate)
else: else:
value = self._getvalue(opt, path, validate) value = self._getvalue(opt, path, validate)
if validate: if config_error is None and validate:
opt.impl_validate(value, self.context(), 'validator' in setting) 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]: 'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False) self.setitem(opt, value, path, is_write=False)
if validate_properties: if validate_properties:
setting.validate_properties(opt, False, False, value=value, path=path, setting.validate_properties(opt, False, False, value=value, path=path,
force_permissive=force_permissive, 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 return value
def __setitem__(self, opt, value): def __setitem__(self, opt, value):
@ -230,7 +253,7 @@ class Values(object):
opt.impl_validate(value, self.context(), opt.impl_validate(value, self.context(),
'validator' in self.context().cfgimpl_get_settings()) 'validator' in self.context().cfgimpl_get_settings())
if opt.impl_is_multi() and not isinstance(value, Multi): 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, self._setvalue(opt, path, value, force_permissive=force_permissive,
is_write=is_write) is_write=is_write)
@ -302,9 +325,9 @@ class Values(object):
clears the cache if necessary clears the cache if necessary
""" """
if only_expired: if only_expired:
self._p_.reset_expired_cache('value', time()) self._p_.reset_expired_cache(int(time()))
else: else:
self._p_.reset_all_cache('value') self._p_.reset_all_cache()
def _get_opt_path(self, opt): 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) 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 # multi types
@ -324,11 +379,13 @@ class Multi(list):
that support item notation for the values of multi options""" that support item notation for the values of multi options"""
__slots__ = ('opt', 'path', 'context') __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 value: the Multi wraps a list value
:param context: the home config that has the values :param context: the home config that has the values
:param opt: the option object that have this Multi value :param opt: the option object that have this Multi value
:param setitem: only if set a value
""" """
self.opt = opt self.opt = opt
self.path = path self.path = path
@ -338,26 +395,34 @@ class Multi(list):
if not isinstance(value, list): if not isinstance(value, list):
value = [value] value = [value]
if validate and self.opt.impl_get_multitype() == multitypes.slave: if validate and self.opt.impl_get_multitype() == multitypes.slave:
value = self._valid_slave(value) value = self._valid_slave(value, setitem)
elif self.opt.impl_get_multitype() == multitypes.master: elif validate and self.opt.impl_get_multitype() == multitypes.master:
self._valid_master(value) self._valid_master(value)
super(Multi, self).__init__(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 #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( masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
self.opt.impl_get_master_slaves()) self.opt.impl_get_master_slaves())
mastervalue = getattr(self.context(), masterp) mastervalue = getattr(self.context(), masterp)
masterlen = len(mastervalue) masterlen = len(mastervalue)
valuelen = len(value) valuelen = len(value)
is_default_owner = not values._is_default_owner(self.path) or setitem
if valuelen > masterlen or (valuelen < masterlen and if valuelen > masterlen or (valuelen < masterlen and
not self.context().cfgimpl_get_values( is_default_owner):
)._is_default_owner(self.path)):
raise SlaveError(_("invalid len for the slave: {0}" raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format( " which has {1} as master").format(
self.opt._name, masterp)) self.opt._name, masterp))
elif valuelen < masterlen: elif valuelen < masterlen:
for num in range(0, masterlen - valuelen): for num in range(0, masterlen - valuelen):
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()) value.append(self.opt.impl_getdefault_multi())
#else: same len so do nothing #else: same len so do nothing
return value return value
@ -376,6 +441,15 @@ class Multi(list):
self.opt._name, slave._name)) self.opt._name, slave._name))
elif len(value_slave) < masterlen: elif len(value_slave) < masterlen:
for num in range(0, masterlen - len(value_slave)): for num in range(0, masterlen - len(value_slave)):
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(), value_slave.append(slave.impl_getdefault_multi(),
force=True) force=True)
@ -470,12 +544,15 @@ class Multi(list):
"").format(str(value), "").format(str(value),
self.opt._name, err)) self.opt._name, err))
def pop(self, key, force=False): def pop(self, index, force=False):
"""the list value can be updated (poped) """the list value can be updated (poped)
only if the option is a master only if the option is a master
:param key: index of the element to pop :param index: remove item a index
:return: the requested element :type index: int
:param force: force pop item (withoud check master/slave)
:type force: boolean
:returns: item at index
""" """
if not force: if not force:
if self.opt.impl_get_multitype() == multitypes.slave: if self.opt.impl_get_multitype() == multitypes.slave:
@ -488,8 +565,8 @@ class Multi(list):
#get multi without valid properties #get multi without valid properties
values.getitem(slave, values.getitem(slave,
validate_properties=False validate_properties=False
).pop(key, force=True) ).pop(index, force=True)
#set value without valid properties #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) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
return ret return ret

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: \n"
"Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n" "Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -11,18 +11,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n" "X-Generator: Poedit 1.5.4\n"
#: tiramisu/autolib.py:49 #: tiramisu/autolib.py:58
msgid "no config specified but needed" msgid "no config specified but needed"
msgstr "aucune config spécifié alors que c'est nécessaire" msgstr "aucune config spécifié alors que c'est nécessaire"
#: tiramisu/autolib.py:56 #: tiramisu/autolib.py:65
msgid "" msgid ""
"unable to carry out a calculation, option {0} has properties: {1} for: {2}" "unable to carry out a calculation, option {0} has properties: {1} for: {2}"
msgstr "" msgstr ""
"impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : " "impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : "
"{2}" "{2}"
#: tiramisu/autolib.py:65 #: tiramisu/autolib.py:74
msgid "" msgid ""
"unable to carry out a calculation, option value with multi types must have " "unable to carry out a calculation, option value with multi types must have "
"same length for: {0}" "same length for: {0}"
@ -30,86 +30,79 @@ msgstr ""
"impossible d'effectuer le calcul, valeur d'un option avec le type multi doit " "impossible d'effectuer le calcul, valeur d'un option avec le type multi doit "
"avoir la même longueur pour : {0}" "avoir la même longueur pour : {0}"
#: tiramisu/config.py:45 #: tiramisu/config.py:47
msgid "descr must be an optiondescription, not {0}" msgid "descr must be an optiondescription, not {0}"
msgstr "descr doit être une optiondescription pas un {0}" msgstr "descr doit être une optiondescription pas un {0}"
#: tiramisu/config.py:118 #: tiramisu/config.py:121
msgid "unknown group_type: {0}" msgid "unknown group_type: {0}"
msgstr "group_type inconnu: {0}" msgstr "group_type inconnu: {0}"
#: tiramisu/config.py:154 #: tiramisu/config.py:157
msgid "no optiondescription for this config (may be metaconfig without meta)" msgid ""
"no option description found for this config (may be metaconfig without meta)"
msgstr "" 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 #: tiramisu/config.py:311
msgid "unknown type_ type {0} for _find" msgid "unknown type_ type {0}for _find"
msgstr "type_ type {0} pour _find inconnu" msgstr "type_ type {0} pour _find inconnu"
#: tiramisu/config.py:351 #: tiramisu/config.py:350
msgid "no option found in config with these criteria" msgid "no option found in config with these criteria"
msgstr "aucune option trouvée dans la config avec ces critères" 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" msgid "make_dict can't filtering with value without option"
msgstr "make_dict ne peut filtrer sur une valeur mais sans 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}" msgid "unexpected path {0}, should start with {1}"
msgstr "chemin imprévu {0}, devrait commencer par {1}" msgstr "chemin imprévu {0}, devrait commencer par {1}"
#: tiramisu/config.py:527 #: tiramisu/config.py:481
msgid "metaconfig's children must be a list" msgid "opt in getowner must be an option not {0}"
msgstr "enfants d'une metaconfig doit être une liste" msgstr "opt dans getowner doit être une option pas {0}"
#: tiramisu/config.py:532 #: tiramisu/option.py:71
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/option.py:70
msgid "{0} has no attribute impl_set_information" msgid "{0} has no attribute impl_set_information"
msgstr "{0} n'a pas d'attribut 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 #: 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" msgid "{0} has no attribute impl_get_information"
msgstr "{0} n'a pas d'attribut 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" msgid "invalid name: {0} for option"
msgstr "nom invalide : {0} pour l'option" msgstr "nom invalide : {0} pour l'option"
#: tiramisu/option.py:134 #: tiramisu/option.py:169
msgid "validator must be a function" msgid "validator must be a function"
msgstr "validator doit être une fonction" 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}" msgid "a default_multi is set whereas multi is False in option: {0}"
msgstr "" msgstr ""
"une default_multi est renseigné alors que multi est False dans l'option : {0}" "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}" msgid "invalid default_multi value {0} for option {1}: {2}"
msgstr "la valeur default_multi est invalide {0} pour l'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" 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é" 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 "" msgid ""
"params defined for a callback function but no callback defined yet for " "params defined for a callback function but no callback defined yet for "
"option {0}" "option {0}"
@ -117,183 +110,183 @@ msgstr ""
"params définit pour une fonction callback mais par de callback défini encore " "params définit pour une fonction callback mais par de callback défini encore "
"pour l'option {0}" "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" msgid "invalid properties type {0} for {1}, must be a tuple"
msgstr "type des properties invalide {0} pour {1}, doit être un 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}" msgid "invalid value {0} for option {1} for object {2}"
msgstr "valeur invalide {0} pour l'option {1} pour l'objet {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}" msgid "invalid value {0} for option {1}: {2}"
msgstr "valeur invalide {0} pour l'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" 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" 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" msgid "invalid value {0} for option {1} must be different as {2} option"
msgstr "" msgstr ""
"valeur invalide {0} pour l'option {1} doit être différent que l'option {2}" "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}" msgid "values must be a tuple for {0}"
msgstr "values doit être un tuple pour {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}" msgid "open_values must be a boolean for {0}"
msgstr "open_values doit être un booléen pour {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" msgid "value {0} is not permitted, only {1} is allowed"
msgstr "valeur {0} n'est pas permit, seules {1} sont autorisées" 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" msgid "value must be a boolean"
msgstr "valeur doit être un booléen" msgstr "valeur doit être un booléen"
#: tiramisu/option.py:421 #: tiramisu/option.py:442
msgid "value must be an integer" msgid "value must be an integer"
msgstr "valeur doit être un numbre" msgstr "valeur doit être un numbre"
#: tiramisu/option.py:431 #: tiramisu/option.py:452
msgid "value must be a float" msgid "value must be a float"
msgstr "valeur doit être un nombre flottant" msgstr "valeur doit être un nombre flottant"
#: tiramisu/option.py:441 #: tiramisu/option.py:462
msgid "value must be a string" msgid "value must be a string, not {0}"
msgstr "valeur doit être une chaîne" msgstr "valeur doit être une chaîne, pas {0}"
#: tiramisu/option.py:452 #: tiramisu/option.py:480
msgid "value must be an unicode" msgid "value must be an unicode"
msgstr "valeur doit être une valeur 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}" msgid "malformed symlinkoption must be an option for symlink {0}"
msgstr "symlinkoption mal formé doit être une option pour symlink {0}" msgstr "symlinkoption mal formé doit être une option pour symlink {0}"
#: tiramisu/option.py:497 #: tiramisu/option.py:526
msgid "IP mustn't not be in reserved class" msgid "IP shall not be in reserved class"
msgstr "IP ne doit pas être d'une classe reservée" 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" msgid "IP must be in private class"
msgstr "IP doit être dans la classe privée" msgstr "IP doit être dans la classe privée"
#: tiramisu/option.py:535 #: tiramisu/option.py:566
msgid "inconsistency in allowed range" msgid "inconsistency in allowed range"
msgstr "inconsistence dans la plage autorisée" msgstr "inconsistence dans la plage autorisée"
#: tiramisu/option.py:540 #: tiramisu/option.py:571
msgid "max value is empty" msgid "max value is empty"
msgstr "valeur maximum est vide" msgstr "valeur maximum est vide"
#: tiramisu/option.py:576 #: tiramisu/option.py:608
msgid "network mustn't not be in reserved class" msgid "network shall not be in reserved class"
msgstr "réseau ne doit pas être dans la classe reservée" 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" 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" 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" 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" 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})" msgid "invalid IP {0} ({1}) with netmask {2} ({3})"
msgstr "IP invalide {0} ({1}) avec masque {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})" msgid "invalid network {0} ({1}) with netmask {2} ({3})"
msgstr "réseau invalide {0} ({1}) avec masque {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" msgid "unknown type_ {0} for hostname"
msgstr "type_ inconnu {0} pour le nom d'hôte" 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" msgid "allow_ip must be a boolean"
msgstr "allow_ip doit être un booléen" msgstr "allow_ip doit être un booléen"
#: tiramisu/option.py:671 #: tiramisu/option.py:704
msgid "invalid value for {0}, must have dot" msgid "invalid value for {0}, must have dot"
msgstr "valeur invalide pour {0}, doit avoir un point" 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})" msgid "invalid domainname's length for {0} (max {1})"
msgstr "longueur du nom de domaine invalide pour {0} (maximum {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)" msgid "invalid domainname's length for {0} (min 2)"
msgstr "longueur du nom de domaine invalide pour {0} (minimum 2)" msgstr "longueur du nom de domaine invalide pour {0} (minimum 2)"
#: tiramisu/option.py:680 #: tiramisu/option.py:714
msgid "invalid domainname" msgid "invalid domainname"
msgstr "nom de domaine invalide" msgstr "nom de domaine invalide"
#: tiramisu/option.py:696 #: tiramisu/option.py:731
msgid "invalid name: {0} for optiondescription" msgid "invalid name: {0} for optiondescription"
msgstr "nom invalide : {0} pour l'optiondescription" msgstr "nom invalide : {0} pour l'optiondescription"
#: tiramisu/option.py:707 #: tiramisu/option.py:743
msgid "duplicate option name: {0}" msgid "duplicate option name: {0}"
msgstr "nom de l'option dupliqué : {0}" msgstr "nom de l'option dupliqué : {0}"
#: tiramisu/option.py:731 #: tiramisu/option.py:769
msgid "unknown Option {0} in OptionDescription {1}" msgid "unknown Option {0} in OptionDescription {1}"
msgstr "Option {} inconnue pour l'OptionDescription{}" msgstr "Option {} inconnue pour l'OptionDescription{}"
#: tiramisu/option.py:795 #: tiramisu/option.py:820
msgid "duplicate option: {0}" msgid "duplicate option: {0}"
msgstr "option dupliquée : {0}" msgstr "option dupliquée : {0}"
#: tiramisu/option.py:805 #: tiramisu/option.py:850
msgid "no option for path {0}" msgid "no option for path {0}"
msgstr "pas d'option pour le chemin {0}" msgstr "pas d'option pour le chemin {0}"
#: tiramisu/option.py:811 #: tiramisu/option.py:856
msgid "no option {0} found" msgid "no option {0} found"
msgstr "pas d'option {0} trouvée" 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})" 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})" 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" msgid "master group {0} shall not have a subgroup"
msgstr "groupe maître {0} ne doit pas avoir de sous-groupe" 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" msgid "master group {0} shall not have a symlinkoption"
msgstr "groupe maître {0} ne doit pas avoir de 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" msgid "not allowed option {0} in group {1}: this option is not a multi"
msgstr "" msgstr ""
"option non autorisée {0} dans le groupe {1} : cette option n'est pas une " "option non autorisée {0} dans le groupe {1} : cette option n'est pas une "
"multi" "multi"
#: tiramisu/option.py:849 #: tiramisu/option.py:896
msgid "master group with wrong master name for {0}" msgid "master group with wrong master name for {0}"
msgstr "le groupe maître avec un nom de maître éroné pour {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}" msgid "no child has same nom has master group for: {0}"
msgstr "pas d'enfant avec le nom du groupe maître pour {0} " msgstr "pas d'enfant avec le nom du groupe maître pour {0} "
#: tiramisu/option.py:860 #: tiramisu/option.py:908
msgid "not allowed group_type : {0}" msgid "group_type: {0} not allowed"
msgstr "group_type non autorisé : {0}" 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" msgid "malformed requirements type for option: {0}, must be a dict"
msgstr "" msgstr ""
"type requirements malformé pour l'option : {0}, doit être un dictionnaire" "type requirements malformé pour l'option : {0}, doit être un dictionnaire"
#: tiramisu/option.py:905 #: tiramisu/option.py:962
msgid "" msgid ""
"malformed requirements for option: {0} require must have option, expected " "malformed requirements for option: {0} require must have option, expected "
"and action keys" "and action keys"
@ -301,68 +294,87 @@ msgstr ""
"requirements malformé pour l'option : {0} l'exigence doit avoir les clefs " "requirements malformé pour l'option : {0} l'exigence doit avoir les clefs "
"option, exptected et action" "option, exptected et action"
#: tiramisu/option.py:910 #: tiramisu/option.py:967
msgid "malformed requirements for option: {0} inverse must be boolean" msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr "requirements malformé pour l'option : {0} inverse doit être un booléen" 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" msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr "requirements malformé pour l'option : {0} transitive doit être booléen" 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" msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr "" msgstr ""
"requirements malformé pour l'option : {0} same_action doit être un booléen" "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}" msgid "malformed requirements must be an option in option {0}"
msgstr "requirements malformé doit être une option dans l'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" msgid "malformed requirements option {0} should not be a multi"
msgstr "requirements malformé l'option {0} ne doit pas être une multi" msgstr "requirements malformé l'option {0} ne doit pas être une multi"
#: tiramisu/option.py:932 #: tiramisu/option.py:988
msgid "" msgid ""
"malformed requirements second argument must be valid for option {0}: {1}" "malformed requirements second argument must be valid for option {0}: {1}"
msgstr "" msgstr ""
"requirements malformé deuxième argument doit être valide pour l'option {0} : " "requirements malformé deuxième argument doit être valide pour l'option {0} : "
"{1}" "{1}"
#: tiramisu/option.py:936 #: tiramisu/option.py:993
msgid "inconsistency in action types for option: {0} action: {1}" msgid "inconsistency in action types for option: {0} action: {1}"
msgstr "incohérence dans les types action pour l'option : {0} action {1}" msgstr "incohérence dans les types action pour l'option : {0} action {1}"
#: tiramisu/setting.py:45 #: tiramisu/setting.py:47
msgid "can't rebind group ({})" msgid "storage_type is already set, cannot rebind it"
msgstr "ne peut reconsolider un groupe ({0})" msgstr "storage_type est déjà défini, impossible de le redéfinir"
#: tiramisu/setting.py:50 #: tiramisu/setting.py:67
msgid "can't unbind group ({})" msgid "can't rebind {0}"
msgstr "ne peut délier un groupe ({0})" msgstr "ne peut redéfinir ({0})"
#: tiramisu/setting.py:210 #: 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: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" 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" 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" msgid "cannot change the value for option {0} this option is frozen"
msgstr "" msgstr ""
"ne peut modifié la valeur de l'option {0} cette option n'est pas modifiable" "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}" 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}" 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" msgid "permissive must be a tuple"
msgstr "permissive doit être un 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}" msgid "invalid generic owner {0}"
msgstr "invalide owner générique {0}" msgstr "invalide owner générique {0}"
#: tiramisu/setting.py:367 #: tiramisu/setting.py:503
msgid "" msgid ""
"malformed requirements imbrication detected for option: '{0}' with " "malformed requirements imbrication detected for option: '{0}' with "
"requirement on: '{1}'" "requirement on: '{1}'"
@ -370,48 +382,81 @@ msgstr ""
"imbrication de requirements malformé detectée pour l'option : '{0}' avec " "imbrication de requirements malformé detectée pour l'option : '{0}' avec "
"requirement sur : '{1}'" "requirement sur : '{1}'"
#: tiramisu/setting.py:377 #: tiramisu/setting.py:515
msgid "option '{0}' has requirement's property error: {1} {2}" 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}" msgstr "l'option '{0}' a une erreur de propriété pour le requirement : {1} {2}"
#: tiramisu/setting.py:383 #: tiramisu/storage/dictionary/storage.py:37
msgid "required option not found: {0}" msgid "dictionary storage cannot delete session"
msgstr "option requise non trouvée : {0}" msgstr ""
"impossible de supprimer une session dans un espace de stockage dictionary"
#: tiramisu/value.py:206 #: 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:284
msgid "no value for {0} cannot change owner to {1}" msgid "no value for {0} cannot change owner to {1}"
msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {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" 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" 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" msgid "invalid len for the master: {0} which has {1} as slave with greater len"
msgstr "" msgstr ""
"longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus " "longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus "
"grande longueur" "grande longueur"
#: tiramisu/value.py:306 #: tiramisu/value.py:394
msgid "cannot append a value on a multi option {0} which is a slave" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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" 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}"

View File

@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,375 +15,395 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
#: tiramisu/autolib.py:49 #: tiramisu/autolib.py:58
msgid "no config specified but needed" msgid "no config specified but needed"
msgstr "" msgstr ""
#: tiramisu/autolib.py:56 #: tiramisu/autolib.py:65
msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}" msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}"
msgstr "" 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}" msgid "unable to carry out a calculation, option value with multi types must have same length for: {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:45 #: tiramisu/config.py:47
msgid "descr must be an optiondescription, not {0}" msgid "descr must be an optiondescription, not {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:118 #: tiramisu/config.py:121
msgid "unknown group_type: {0}" msgid "unknown group_type: {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:154 #: tiramisu/config.py:157
msgid "no optiondescription for this config (may be metaconfig without meta)" msgid "no option description found for this config (may be metaconfig without meta)"
msgstr "" msgstr ""
#: tiramisu/config.py:312 #: tiramisu/config.py:311
msgid "unknown type_ type {0} for _find" msgid "unknown type_ type {0}for _find"
msgstr "" msgstr ""
#: tiramisu/config.py:351 #: tiramisu/config.py:350
msgid "no option found in config with these criteria" msgid "no option found in config with these criteria"
msgstr "" msgstr ""
#: tiramisu/config.py:394 #: tiramisu/config.py:400
msgid "make_dict can't filtering with value without option" msgid "make_dict can't filtering with value without option"
msgstr "" msgstr ""
#: tiramisu/config.py:414 #: tiramisu/config.py:421
msgid "unexpected path {0}, should start with {1}" msgid "unexpected path {0}, should start with {1}"
msgstr "" msgstr ""
#: tiramisu/config.py:527 #: tiramisu/config.py:481
msgid "metaconfig's children must be a list" msgid "opt in getowner must be an option not {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:532 #: tiramisu/option.py:71
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
msgid "{0} has no attribute impl_set_information" msgid "{0} has no attribute impl_set_information"
msgstr "" msgstr ""
#: tiramisu/option.py:84 #: tiramisu/option.py:86
msgid "Information's item not found: {0}" msgid "information's item not found: {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:86 #: tiramisu/option.py:89
msgid "{0} has no attribute impl_get_information" msgid "{0} has no attribute impl_get_information"
msgstr "" 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" msgid "invalid name: {0} for option"
msgstr "" msgstr ""
#: tiramisu/option.py:134 #: tiramisu/option.py:218
msgid "validator must be a function" msgid "validator must be a function"
msgstr "" msgstr ""
#: tiramisu/option.py:141 #: tiramisu/option.py:225
msgid "a default_multi is set whereas multi is False in option: {0}" msgid "a default_multi is set whereas multi is False in option: {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:147 #: tiramisu/option.py:231
msgid "invalid default_multi value {0} for option {1}: {2}" msgid "invalid default_multi value {0} for option {1}: {2}"
msgstr "" msgstr ""
#: tiramisu/option.py:150 #: tiramisu/option.py:236
msgid "default value not allowed if option: {0} is calculated" msgid "default value not allowed if option: {0} is calculated"
msgstr "" msgstr ""
#: tiramisu/option.py:153 #: tiramisu/option.py:239
msgid "params defined for a callback function but no callback defined yet for option {0}" msgid "params defined for a callback function but no callback defined yet for option {0}"
msgstr "" 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" msgid "invalid properties type {0} for {1}, must be a tuple"
msgstr "" msgstr ""
#: tiramisu/option.py:273 #: tiramisu/option.py:334
msgid "invalid value {0} for option {1} for object {2}" msgid "invalid value {0} for option {1} for object {2}"
msgstr "" 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}" msgid "invalid value {0} for option {1}: {2}"
msgstr "" msgstr ""
#: tiramisu/option.py:290 #: tiramisu/option.py:354
msgid "invalid value {0} for option {1} which must be a list" msgid "invalid value {0} for option {1} which must be a list"
msgstr "" msgstr ""
#: tiramisu/option.py:354 #: tiramisu/option.py:423
msgid "invalid value {0} for option {1} must be different as {2} option" msgid "invalid value {0} for option {1} must be different as {2} option"
msgstr "" msgstr ""
#: tiramisu/option.py:376 #: tiramisu/option.py:445
msgid "values must be a tuple for {0}" msgid "values must be a tuple for {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:379 #: tiramisu/option.py:448
msgid "open_values must be a boolean for {0}" msgid "open_values must be a boolean for {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:400 #: tiramisu/option.py:469
msgid "value {0} is not permitted, only {1} is allowed" msgid "value {0} is not permitted, only {1} is allowed"
msgstr "" msgstr ""
#: tiramisu/option.py:411 #: tiramisu/option.py:481
msgid "value must be a boolean" msgid "value must be a boolean"
msgstr "" msgstr ""
#: tiramisu/option.py:421 #: tiramisu/option.py:491
msgid "value must be an integer" msgid "value must be an integer"
msgstr "" msgstr ""
#: tiramisu/option.py:431 #: tiramisu/option.py:501
msgid "value must be a float" msgid "value must be a float"
msgstr "" msgstr ""
#: tiramisu/option.py:441 #: tiramisu/option.py:511
msgid "value must be a string" msgid "value must be a string, not {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:452 #: tiramisu/option.py:529
msgid "value must be an unicode" msgid "value must be an unicode"
msgstr "" msgstr ""
#: tiramisu/option.py:463 #: tiramisu/option.py:539
msgid "malformed symlinkoption must be an option for symlink {0}" msgid "malformed symlinkoption must be an option for symlink {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:497 #: tiramisu/option.py:581
msgid "IP mustn't not be in reserved class" msgid "IP shall not be in reserved class"
msgstr "" msgstr ""
#: tiramisu/option.py:499 #: tiramisu/option.py:583
msgid "IP must be in private class" msgid "IP must be in private class"
msgstr "" msgstr ""
#: tiramisu/option.py:535 #: tiramisu/option.py:621
msgid "inconsistency in allowed range" msgid "inconsistency in allowed range"
msgstr "" msgstr ""
#: tiramisu/option.py:540 #: tiramisu/option.py:626
msgid "max value is empty" msgid "max value is empty"
msgstr "" msgstr ""
#: tiramisu/option.py:576 #: tiramisu/option.py:663
msgid "network mustn't not be in reserved class" msgid "network shall not be in reserved class"
msgstr "" msgstr ""
#: tiramisu/option.py:608 #: tiramisu/option.py:695
msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP"
msgstr "" msgstr ""
#: tiramisu/option.py:612 #: tiramisu/option.py:700
msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network"
msgstr "" msgstr ""
#: tiramisu/option.py:617 #: tiramisu/option.py:705
msgid "invalid IP {0} ({1}) with netmask {2} ({3})" msgid "invalid IP {0} ({1}) with netmask {2} ({3})"
msgstr "" msgstr ""
#: tiramisu/option.py:619 #: tiramisu/option.py:707
msgid "invalid network {0} ({1}) with netmask {2} ({3})" msgid "invalid network {0} ({1}) with netmask {2} ({3})"
msgstr "" msgstr ""
#: tiramisu/option.py:639 #: tiramisu/option.py:727
msgid "unknown type_ {0} for hostname" msgid "unknown type_ {0} for hostname"
msgstr "" msgstr ""
#: tiramisu/option.py:642 #: tiramisu/option.py:730
msgid "allow_ip must be a boolean" msgid "allow_ip must be a boolean"
msgstr "" msgstr ""
#: tiramisu/option.py:671 #: tiramisu/option.py:759
msgid "invalid value for {0}, must have dot" msgid "invalid value for {0}, must have dot"
msgstr "" msgstr ""
#: tiramisu/option.py:674 #: tiramisu/option.py:762
msgid "invalid domainname's length for {0} (max {1})" msgid "invalid domainname's length for {0} (max {1})"
msgstr "" msgstr ""
#: tiramisu/option.py:676 #: tiramisu/option.py:765
msgid "invalid domainname's length for {0} (min 2)" msgid "invalid domainname's length for {0} (min 2)"
msgstr "" msgstr ""
#: tiramisu/option.py:680 #: tiramisu/option.py:769
msgid "invalid domainname" msgid "invalid domainname"
msgstr "" msgstr ""
#: tiramisu/option.py:696 #: tiramisu/option.py:787
msgid "invalid name: {0} for optiondescription" msgid "invalid name: {0} for optiondescription"
msgstr "" msgstr ""
#: tiramisu/option.py:707 #: tiramisu/option.py:799
msgid "duplicate option name: {0}" msgid "duplicate option name: {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:731 #: tiramisu/option.py:825
msgid "unknown Option {0} in OptionDescription {1}" msgid "unknown Option {0} in OptionDescription {1}"
msgstr "" msgstr ""
#: tiramisu/option.py:795 #: tiramisu/option.py:874
msgid "duplicate option: {0}" msgid "duplicate option: {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:805 #: tiramisu/option.py:904
msgid "no option for path {0}" msgid "no option for path {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:811 #: tiramisu/option.py:910
msgid "no option {0} found" msgid "no option {0} found"
msgstr "" msgstr ""
#: tiramisu/option.py:821 #: tiramisu/option.py:920
msgid "cannot change group_type if already set (old {0}, new {1})" msgid "cannot change group_type if already set (old {0}, new {1})"
msgstr "" msgstr ""
#: tiramisu/option.py:833 #: tiramisu/option.py:933
msgid "master group {0} shall not have a subgroup" msgid "master group {0} shall not have a subgroup"
msgstr "" msgstr ""
#: tiramisu/option.py:836 #: tiramisu/option.py:936
msgid "master group {0} shall not have a symlinkoption" msgid "master group {0} shall not have a symlinkoption"
msgstr "" msgstr ""
#: tiramisu/option.py:839 #: tiramisu/option.py:939
msgid "not allowed option {0} in group {1}: this option is not a multi" msgid "not allowed option {0} in group {1}: this option is not a multi"
msgstr "" msgstr ""
#: tiramisu/option.py:849 #: tiramisu/option.py:950
msgid "master group with wrong master name for {0}" msgid "master group with wrong master name for {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:857 #: tiramisu/option.py:959
msgid "no child has same nom has master group for: {0}" msgid "no child has same nom has master group for: {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:860 #: tiramisu/option.py:962
msgid "not allowed group_type : {0}" msgid "group_type: {0} not allowed"
msgstr "" msgstr ""
#: tiramisu/option.py:889 #: tiramisu/option.py:1021
msgid "malformed requirements type for option: {0}, must be a dict" msgid "malformed requirements type for option: {0}, must be a dict"
msgstr "" msgstr ""
#: tiramisu/option.py:905 #: tiramisu/option.py:1037
msgid "malformed requirements for option: {0} require must have option, expected and action keys" msgid "malformed requirements for option: {0} require must have option, expected and action keys"
msgstr "" msgstr ""
#: tiramisu/option.py:910 #: tiramisu/option.py:1042
msgid "malformed requirements for option: {0} inverse must be boolean" msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr "" msgstr ""
#: tiramisu/option.py:914 #: tiramisu/option.py:1046
msgid "malformed requirements for option: {0} transitive must be boolean" msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr "" msgstr ""
#: tiramisu/option.py:918 #: tiramisu/option.py:1050
msgid "malformed requirements for option: {0} same_action must be boolean" msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr "" msgstr ""
#: tiramisu/option.py:923 #: tiramisu/option.py:1054
msgid "malformed requirements must be an option in option {0}" msgid "malformed requirements must be an option in option {0}"
msgstr "" msgstr ""
#: tiramisu/option.py:926 #: tiramisu/option.py:1057
msgid "malformed requirements option {0} should not be a multi" msgid "malformed requirements option {0} should not be a multi"
msgstr "" msgstr ""
#: tiramisu/option.py:932 #: tiramisu/option.py:1063
msgid "malformed requirements second argument must be valid for option {0}: {1}" msgid "malformed requirements second argument must be valid for option {0}: {1}"
msgstr "" msgstr ""
#: tiramisu/option.py:936 #: tiramisu/option.py:1068
msgid "inconsistency in action types for option: {0} action: {1}" msgid "inconsistency in action types for option: {0} action: {1}"
msgstr "" msgstr ""
#: tiramisu/setting.py:45 #: tiramisu/setting.py:47
msgid "can't rebind group ({})" msgid "storage_type is already set, cannot rebind it"
msgstr "" msgstr ""
#: tiramisu/setting.py:50 #: tiramisu/setting.py:67
msgid "can't unbind group ({})" msgid "can't rebind {0}"
msgstr "" msgstr ""
#: tiramisu/setting.py:210 #: 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:215
msgid "option {0} not already exists in storage {1}"
msgstr ""
#: tiramisu/setting.py:282
msgid "opt and all_properties must not be set together in reset" msgid "opt and all_properties must not be set together in reset"
msgstr "" 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" msgid "cannot change the value for option {0} this option is frozen"
msgstr "" msgstr ""
#: tiramisu/setting.py:298 #: tiramisu/setting.py:397
msgid "trying to access to an option named: {0} with properties {1}" msgid "trying to access to an option named: {0} with properties {1}"
msgstr "" msgstr ""
#: tiramisu/setting.py:307 #: tiramisu/setting.py:415
msgid "permissive must be a tuple" msgid "permissive must be a tuple"
msgstr "" msgstr ""
#: tiramisu/setting.py:314 tiramisu/value.py:208 #: tiramisu/setting.py:422 tiramisu/value.py:277
msgid "invalid generic owner {0}" msgid "invalid generic owner {0}"
msgstr "" msgstr ""
#: tiramisu/setting.py:367 #: tiramisu/setting.py:503
msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'" msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'"
msgstr "" msgstr ""
#: tiramisu/setting.py:377 #: tiramisu/setting.py:515
msgid "option '{0}' has requirement's property error: {1} {2}" msgid "option '{0}' has requirement's property error: {1} {2}"
msgstr "" msgstr ""
#: tiramisu/setting.py:383 #: tiramisu/storage/dictionary/storage.py:37
msgid "required option not found: {0}" msgid "dictionary storage cannot delete session"
msgstr "" msgstr ""
#: tiramisu/value.py:206 #: tiramisu/storage/dictionary/storage.py:46
msgid "session already used"
msgstr ""
#: 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}" msgid "no value for {0} cannot change owner to {1}"
msgstr "" msgstr ""
#: tiramisu/value.py:270 #: tiramisu/value.py:356
msgid "invalid len for the slave: {0} which has {1} as master" msgid "invalid len for the slave: {0} which has {1} as master"
msgstr "" msgstr ""
#: tiramisu/value.py:286 #: tiramisu/value.py:373
msgid "invalid len for the master: {0} which has {1} as slave with greater len" msgid "invalid len for the master: {0} which has {1} as slave with greater len"
msgstr "" msgstr ""
#: tiramisu/value.py:306 #: tiramisu/value.py:394
msgid "cannot append a value on a multi option {0} which is a slave" msgid "cannot append a value on a multi option {0} which is a slave"
msgstr "" msgstr ""
#: tiramisu/value.py:334 #: tiramisu/value.py:429
msgid "cannot sort multi option {0} if master or slave" msgid "cannot sort multi option {0} if master or slave"
msgstr "" 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" msgid "cannot reverse multi option {0} if master or slave"
msgstr "" msgstr ""
#: tiramisu/value.py:350 #: tiramisu/value.py:450
msgid "cannot insert multi option {0} if master or slave" msgid "cannot insert multi option {0} if master or slave"
msgstr "" msgstr ""
#: tiramisu/value.py:358 #: tiramisu/value.py:458
msgid "cannot extend multi option {0} if master or slave" msgid "cannot extend multi option {0} if master or slave"
msgstr "" msgstr ""
#: tiramisu/value.py:381 #: tiramisu/value.py:482
msgid "cannot pop a value on a multi option {0} which is a slave" msgid "cannot pop a value on a multi option {0} which is a slave"
msgstr "" msgstr ""