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
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)
endif
LAST_TAG := $(shell git describe --tags --abbrev=0)
VERSION := $(shell echo $(LAST_TAG) | awk -F'/' '{print $$2}' || true)
VERSION_FILE := version.in
VERSION := `cat VERSION`
define gettext
if command -v pygettext >/dev/null 2>&1 ; then \
P="pygettext" ; \
else \
P="pygettext.py" ; \
fi ; \
$$P -p translations/ -o $(PACKAGE).pot `find $(PACKAGE)/ -name "*.py"`
endef
# Build translation files
define build_translation
if [ -d ${1} ]; then \
for f in `find ${1} -name "*.po"`; do \
msgfmt -o `dirname $$f`/`basename -s ".po" $$f`.mo $$f || true; \
msgfmt -o `dirname $$f`/`basename $$f ".po"`.mo $$f || true; \
done; \
fi
endef
# Install Traduction
define install_translation
if [ -d ${1} ]; then \
@ -39,38 +48,33 @@ define install_translation
fi
endef
all:
all: build-lang
clean:
$(RM) -r build
$(RM) -r tiramisu.egg-info/
$(RM) -r $(PACKAGE).egg-info/
$(RM) -r $(TRADUC_DIR)/*/*.mo
#test: clean
# py.test
# Build or update Portable Object Base Translation for gettext
build-pot:
$(call gettext)
build-lang:
$(call build_translation, $(TRADUC_DIR))
install-lang: build-lang
install-lang:
$(INSTALL_DIR) $(TRADUC_DEST)
$(call install_translation, $(TRADUC_DIR))
install: version.in install-lang
install: install-lang
python setup.py install --no-compile $(PYTHON_OPTS)
dist:
git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ HEAD | gzip -9 > $(PACKAGE)-$(VERSION).tar.gz
# List in .PHONY to force generation at each call
version.in:
@if test -n $(VERSION) ; then \
echo -n $(VERSION) > $(VERSION_FILE) ; \
fi
@if test ! -s $(VERSION_FILE); then \
echo -n '0.0-dev' > $(VERSION_FILE); \
fi
dist: version.in
git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ -o $(PACKAGE)-$(VERSION).tar $(LAST_TAG) \
&& tar --xform "s,\(.*\),$(PACKAGE)-$(VERSION)/\1," -f $(PACKAGE)-$(VERSION).tar -r version.in \
&& gzip -9 $(PACKAGE)-$(VERSION).tar
.PHONY: all clean test install version.in dist
.PHONY: all clean build-pot build-lang install-lang install dist

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.autolib
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 :
- :class:`tiramisu.config.Config` which is the whole configuration entry point
- :class:`tiramisu.option.Option` stands for the option types
- :class:`tiramisu.option.OptionDescription` is the shema, the option's structure
- :class:`tiramisu.config.Config` which is the whole configuration entry point
.. image:: config.png
Accessing the `Option`'s
-------------------------
The :class:`~tiramisu.config.Config` object attribute access notation stands for
the value of the configuration's :class:`~tiramisu.option.Option`. That is, the
the value of the configuration's :class:`~tiramisu.option.Option`.
:class:`~tiramisu.config.Config`'s object attribute is the name of the option,
and the value is the value accessed by the `__getattr__` attribute access
mechanism.
@ -49,7 +51,7 @@ are organized into a tree into nested
as does every option group. The parts of the full name of the option are
separated by dots: e.g. ``cfg.optgroup.optname``.
Let's make the protocol of accessing a config's attribute explicit
Let's make the protocol of accessing a `Config`'s attribute explicit
(because explicit is better than implicit):
1. If the option has not been declared, an `AttributeError` is raised,
@ -67,22 +69,11 @@ But there are special exceptions. We will see later on that an option can be a
:term:`mandatory option`. A mandatory option is an option that must have a value
defined.
Appart from this case, if no value have been set yet, the value is `None`. When
the option is called to retrieve a value, an exception is raised.
Setting the value of an option
------------------------------
What if a value has been set and `None` is to be returned again ? Don't worry,
an option value can be reseted::
::
>>> cfg.cfgimpl_get_values().reset(gcdummy)
>>> cfg.dummy
False
Setting the values of the options
----------------------------------------
An important part of the setting of the configuration consists of setting the
values of the configuration options. There are different ways of setting values,
An important part of the setting's configuration consists of setting the
value's option. There are different ways of setting values,
the first one is of course the `__setattr__` method
::
@ -110,14 +101,14 @@ adhere to the option description).
Common manipulations
------------------------
Let's perform some common manipulation on some options:
Let's perform some common manipulation on some options
>>> from tiramisu.config import Config
>>> from tiramisu.option import UnicodeOption, OptionDescription
>>>
>>> #
>>> var1 = UnicodeOption('var1', 'first variable')
>>> var2 = UnicodeOption('var2', '', u'value')
>>>
>>> #
>>> od1 = OptionDescription('od1', 'first OD', [var1, var2])
>>> rootod = OptionDescription('rootod', '', [od1])
@ -138,11 +129,10 @@ None
>>> print c.od1.var2
value
let's modify a value (careful to the value's type...)
let's modify a value (be careful to the value's type...)
>>> c.od1.var1 = 'value'
Traceback (most recent call last):
[...]
ValueError: invalid value value for option var1
>>> c.od1.var1 = u'value'
>>> print c.od1.var1
@ -158,19 +148,17 @@ let's come back to the default value
value
The value is saved in a :class:`~tiramisu.value.Value` object. It is on this
object that we have to trigger the `reset`, wich take the option itself
object that we have to trigger the `reset`, which take the option itself
(`var2`) as a parameter.
On the other side, in the `read_only` mode, it is not possible to modify the value::
On the other side, in the `read_only` mode, it is not possible to modify the value
>>> c.read_only()
>>> c.od1.var2 = u'value2'
Traceback (most recent call last):
tiramisu.error.PropertiesOptionError: cannot change the value for option var2 this option is frozen
>>> c.read_only()
>>> c.od1.var2 = u'value2'
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError:
cannot change the value to var2
for option ['frozen'] this option is frozen
let's retrieve the option `var1` description
>>> var1.impl_get_information('doc')
@ -200,7 +188,7 @@ That's why a tree of options can easily be searched. First, let's build such a t
>>> c = Config(rootod)
>>> c.read_write()
Second, let's find an option by his name::
Second, let's find an option by it's name::
>>> print c.find(byname='var1')
[<tiramisu.option.UnicodeOption object at 0x7ff1bf7d6ef0>,
@ -248,7 +236,7 @@ If the organisation in a tree is not important,
{'var5': None, 'var4': None, 'var6': None, 'var1': u'value', 'var3': None,
'var2': None}
.. note:: carefull with this `flatten` parameter, here we have just lost
.. note:: be carefull with this `flatten` parameter, here we have just lost
two options named `var1`
One can export only interesting parts of a tree of options into a dict, for
@ -265,7 +253,7 @@ and of course, :meth:`~config.SubConfig.make_dict()` can be called in a subtree:
>>> print c.od1.make_dict(withoption='var1')
{'var1': None, 'var3': None, 'var2': None}
the owners
The owners
~~~~~~~~~~~
.. glossary::
@ -283,24 +271,36 @@ the owners
Then let's retrieve the owner associated to an option::
>>> print c.getowner('var1')
default
>>> c.od1.var1 = u'non'
>>> print c.getowner('var1')
user
>>> del(c.var1)
>>> print c.getowner('var1')
default
the properties
~~~~~~~~~~~~~~~~
>>> print c.getowner(var1)
default
>>> c.od1.var1 = u'no'
>>> print c.getowner(var1)
user
>>> del(c.var1)
>>> print c.getowner(var1)
default
You can create your own owner, for example to distinguish modification made by
one user to an other one's.
>>> from tiramisu.setting import owners
>>> owners.addowner('toto')
>>> c.cfgimpl_get_settings().setowner(owners.toto)
>>> print c.getowner(var1)
default
>>> c.od1.var1 = u'no'
>>> print c.getowner(var1)
toto
The properties
~~~~~~~~~~~~~~
A property is an information on an option's state.
Let's create options with properties::
>>> var1 = UnicodeOption('var1', '', u'value', properties=('hidden',))
>>> var2 = UnicodeOption('var2', '', properties=('mandatory',))
>>> var3 = UnicodeOption('var3', '', u'value', properties=('frozen', 'inconnu'))
>>> var3 = UnicodeOption('var3', '', u'value', properties=('frozen', 'unknown'))
>>> var4 = UnicodeOption('var4', '', u'value')
>>> od1 = OptionDescription('od1', '', [var1, var2, var3])
>>> od2 = OptionDescription('od2', '', [var4], properties=('hidden',))
@ -314,7 +314,6 @@ with a hidden option::
>>> print c.od1.var1
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var1
with properties ['hidden']
>>> c.read_only()
@ -331,7 +330,6 @@ mode. But in read only mode, an error is raised if no value has been defined::
>>> c.read_only()
>>> print c.od1.var2
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: var2
with properties ['mandatory']
>>> c.read_write()
@ -348,7 +346,6 @@ Let's try to modify a frozen option::
value
>>> c.od1.var3 = u'value2'
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: cannot change the value for option var3 this option is frozen
>>> c.read_only()
>>> print c.od1.var3
@ -357,23 +354,21 @@ Let's try to modify a frozen option::
Tiramisu allows us to use user defined properties. Let's define and use one in
read/write or read only mode::
>>> c.cfgimpl_get_settings().append('inconnu')
>>> c.cfgimpl_get_settings().append('unknown')
>>> print c.od1.var3
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named:
var3 with properties ['inconnu']
>>> c.cfgimpl_get_settings().remove('inconnu')
var3 with properties ['unknown']
>>> c.cfgimpl_get_settings().remove('unknown')
>>> print c.od1.var3
value
Properties can also be defined on an option group, (that is, on an
:term:`option description`), let's hide a group and try to access to it::
Properties can also be defined on an option group (that is, on an
:term:`option description`) let's hide a group and try to access to it::
>>> c.read_write()
>>> print c.od2.var4
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named: od2
with properties ['hidden']
>>> c.read_only()
@ -387,7 +382,6 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property
['hidden']
>>> print c.od1.var1
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named:
var1 with properties ['hidden']
>>> c.cfgimpl_get_settings()[rootod.od1.var1].remove('hidden')
@ -400,7 +394,6 @@ Furthermore, let's retrieve the properties, delete and add the `hidden` property
['hidden']
>>> print c.od1.var1
Traceback (most recent call last):
[...]
tiramisu.error.PropertiesOptionError: trying to access to an option named:
var1 with properties ['hidden']
@ -438,11 +431,10 @@ A multi-option's value can be manipulated like a list::
>>> print c.od1.var1
[u'var1']
But it is not possible to set a value to a multi-option wich is not a list::
But it is not possible to set a value to a multi-option which is not a list::
>>> c.od1.var1 = u'error'
Traceback (most recent call last):
[...]
ValueError: invalid value error for option var1 which must be a list
@ -514,17 +506,14 @@ But it is forbidden to change the lenght of a slave::
slave2 = [u'default', u'default']
>>> c.master.slave1 = [u'new1']
Traceback (most recent call last):
[...]
tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master
>>> c.master.slave1 = [u'new1', u'new2', u'new3']
[...]
tiramisu.error.SlaveError: invalid len for the slave: slave1 which has master.master as master
you have to call the `pop` function on the master::
>>> c.master.master = [u'oui']
Traceback (most recent call last):
[...]
tiramisu.error.SlaveError: invalid len for the master: master which has slave1 as slave with greater len
>>> c.master.master.pop(0)
u'oui'
@ -570,6 +559,6 @@ Here are the (useful) methods on ``Config`` (or `SubConfig`).
A :class:`~config.CommonConfig` is a abstract base class. A
:class:`~config.SubConfig` is an just in time created objects that wraps an
::class:`~option.OptionDescription`. A SubConfig differs from a Config in the
::fact that a config is a root object and has an environnement, a context wich
::defines the different properties, access rules, vs... There is generally only
::one Config, and many SubConfigs.
fact that a config is a root object and has an environnement, a context which
defines the different properties, access rules, vs... There is generally only
one Config, and many SubConfigs.

View File

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

View File

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

View File

@ -14,7 +14,7 @@ introduced...
What is Tiramisu ?
===================
Tiramisu is an options handler and an options controller, wich aims at
Tiramisu is an options handler and an options controller, which aims at
producing flexible and fast options access. The main advantages are its access
rules and the fact that the whole consistency is preserved at any time, see
:doc:`consistency`. There is of course type and structure validations, but also
@ -65,7 +65,7 @@ So by now, we have:
- a namespace (which is `c` here)
- the access of an option's value by the
attribute access way (here `bool`, wich is a boolean option
attribute access way (here `bool`, which is a boolean option
:class:`~tiramisu.option.BoolOption()`.
So, option objects are produced at the entry point `c` and then handed down to

View File

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

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

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
# -*- coding: utf-8 -*-
from distutils.core import setup
from os.path import dirname, abspath, join, normpath, isdir, basename
from os.path import dirname, abspath, join, normpath, isdir
from os import listdir
import os
import subprocess
def fetch_version():
"""Get version from version.in or latest git tag"""
version_file='version.in'
version = "1.0"
git_last_tag_cmd = ['git', 'describe', '--tags', '--abbrev=0']
"""Get version from version.in"""
return file('VERSION', 'r').readline().strip()
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():
"returns all the storage plugins that are living in tiramisu/storage"
here = dirname(abspath(__file__))
storages_path = normpath(join(here, 'tiramisu', 'storage'))
dir_content = [ content for content in listdir(storages_path) \
if not content =='__pycache__']
storages = filter(isdir, [join(storages_path, content) \
dir_content = [content for content in listdir(storages_path)
if not content == '__pycache__']
storages = filter(isdir, [join(storages_path, content)
for content in dir_content])
storage_list = [basename(storage) for storage in storages]
storage_list = ['.'.join(storage.split('/')[-3:]) for storage in storages]
return storage_list
packages = ['tiramisu', 'tiramisu.storage']
packages.extend(return_storages())
setup(
author='cadoles team',
author="Tiramisu's team",
author_email='contact@cadoles.com',
name='tiramisu',
version=fetch_version(),
description='configuration management tool',
url='http://labs.libre-entreprise.org/projects/tiramisu',
description='an options controller tool',
url='http://tiramisu.labs.libre-entreprise.org/',
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Linguistic"
],
long_description="""\
An options controller tool
-------------------------------------
Due to more and more available options required to set up an operating system,
compiler options or whatever, it became quite annoying to hand the necessary
options to where they are actually used and even more annoying to add new
options. To circumvent these problems the configuration control was
introduced...
Tiramisu is an options handler and an options controller, wich aims at
producing flexible and fast options access.
This version requires Python 2.6 or later.
""",
packages=packages
)

View File

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

View File

@ -141,6 +141,7 @@ def test_information_config():
string = 'some informations'
config.impl_set_information('info', string)
assert config.impl_get_information('info') == string
raises(ValueError, "config.impl_get_information('noinfo')")
def test_config_impl_get_path_by_opt():

View File

@ -19,7 +19,6 @@ def make_description():
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Tests', default=False)
wantframework_option = BoolOption('wantframework', 'Test', default=False)
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption])
descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
@ -117,6 +116,23 @@ def test_find_in_config():
#assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy')
def test_find_multi():
b = BoolOption('bool', '', multi=True)
o = OptionDescription('od', '', [b])
conf = Config(o)
raises(AttributeError, "conf.find(byvalue=True)")
raises(AttributeError, "conf.find_first(byvalue=True)")
conf.bool.append(False)
raises(AttributeError, "conf.find(byvalue=True)")
raises(AttributeError, "conf.find_first(byvalue=True)")
conf.bool.append(False)
raises(AttributeError, "conf.find(byvalue=True)")
raises(AttributeError, "conf.find_first(byvalue=True)")
conf.bool.append(True)
assert conf.find(byvalue=True) == [b]
assert conf.find_first(byvalue=True) == b
def test_does_not_find_in_config():
descr = make_description()
conf = Config(descr)

View File

@ -29,6 +29,15 @@ def test_ip_default():
c.a == '88.88.88.88'
def test_ip_reserved():
a = IPOption('a', '')
b = IPOption('b', '', allow_reserved=True)
od = OptionDescription('od', '', [a, b])
c = Config(od)
raises(ValueError, "c.a = '226.94.1.1'")
c.b = '226.94.1.1'
def test_network():
a = NetworkOption('a', '')
od = OptionDescription('od', '', [a])

View File

@ -4,19 +4,27 @@ from py.test import raises
from tiramisu.setting import groups
from tiramisu.config import Config
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription
from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError
StrOption, OptionDescription, SymLinkOption
from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError
def return_val():
return 'val'
def return_list():
def return_concat(*args):
return '.'.join(list(args))
def return_list(value=None):
return ['val', 'val']
def return_value(value):
def return_list2(*args):
return list(args)
def return_value(value=None):
return value
@ -298,18 +306,73 @@ def test_callback():
def test_callback_value():
val1 = StrOption('val1', "", 'val')
val2 = StrOption('val2', "", callback=return_value, callback_params={'': (('val1', False),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2])
val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", callback=return_value, callback_params={'': ('yes',)})
val4 = StrOption('val4', "", callback=return_value, callback_params={'value': ((val1, False),)})
val5 = StrOption('val5', "", callback=return_value, callback_params={'value': ('yes',)})
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1 == 'val'
assert cfg.val2 == 'val'
assert cfg.val4 == 'val'
cfg.val1 = 'new-val'
assert cfg.val1 == 'new-val'
assert cfg.val2 == 'new-val'
assert cfg.val4 == 'new-val'
del(cfg.val1)
assert cfg.val1 == 'val'
assert cfg.val2 == 'val'
assert cfg.val3 == 'yes'
assert cfg.val4 == 'val'
assert cfg.val5 == 'yes'
def test_callback_value_tuple():
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "", 'val2')
val3 = StrOption('val3', "", callback=return_concat, callback_params={'': ((val1, False), (val2, False))})
val4 = StrOption('val4', "", callback=return_concat, callback_params={'': ('yes', 'no')})
raises(ValueError, "StrOption('val4', '', callback=return_concat, callback_params={'value': ('yes', 'no')})")
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1 == 'val1'
assert cfg.val2 == 'val2'
assert cfg.val3 == 'val1.val2'
assert cfg.val4 == 'yes.no'
cfg.val1 = 'new-val'
assert cfg.val3 == 'new-val.val2'
del(cfg.val1)
assert cfg.val3 == 'val1.val2'
def test_callback_value_force_permissive():
val1 = StrOption('val1', "", 'val', properties=('disabled',))
val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val1, True),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3])
cfg = Config(maconfig)
cfg.read_only()
raises(ConfigError, "cfg.val2")
assert cfg.val3 is None
def test_callback_symlink():
val1 = StrOption('val1', "", 'val')
val2 = SymLinkOption('val2', val1)
val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val2, False),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1 == 'val'
assert cfg.val3 == 'val'
cfg.val1 = 'new-val'
assert cfg.val1 == 'new-val'
assert cfg.val3 == 'new-val'
del(cfg.val1)
assert cfg.val1 == 'val'
assert cfg.val3 == 'val'
def test_callback_list():
@ -336,21 +399,28 @@ def test_callback_multi():
def test_callback_multi_value():
val1 = StrOption('val1', "", ['val'], multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1', False),)})
maconfig = OptionDescription('rootconfig', '', [val1, val2])
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), 'yes')})
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1 == ['val']
assert cfg.val2 == ['val']
assert cfg.val4 == ['val', 'yes']
cfg.val1 = ['new-val']
assert cfg.val1 == ['new-val']
assert cfg.val2 == ['new-val']
assert cfg.val4 == ['new-val', 'yes']
cfg.val1.append('new-val2')
assert cfg.val1 == ['new-val', 'new-val2']
assert cfg.val2 == ['new-val', 'new-val2']
assert cfg.val4 == ['new-val', 'yes', 'new-val2', 'yes']
del(cfg.val1)
assert cfg.val1 == ['val']
assert cfg.val2 == ['val']
assert cfg.val3 == ['yes']
assert cfg.val4 == ['val', 'yes']
def test_callback_multi_list():
@ -455,41 +525,67 @@ def test_callback_master_and_slaves_slave_list():
def test_callback_master_and_slaves_value():
val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1.val1', False),)})
interface1 = OptionDescription('val1', '', [val1, val2])
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
interface1 = OptionDescription('val1', '', [val1, val2, val3, val5])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('rootconfig', '', [interface1])
maconfig = OptionDescription('rootconfig', '', [interface1, val4])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val1.val1 == []
assert cfg.val1.val2 == []
assert cfg.val1.val3 == []
assert cfg.val1.val5 == []
#
cfg.val1.val1 = ['val1']
assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val1']
assert cfg.val1.val3 == ['yes']
assert cfg.val1.val5 == ['val10']
#
cfg.val1.val1.append('val2')
assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val1', 'val2']
assert cfg.val1.val3 == ['yes', 'yes']
assert cfg.val1.val5 == ['val10', 'val11']
#
cfg.val1.val1 = ['val1', 'val2', 'val3']
assert cfg.val1.val1 == ['val1', 'val2', 'val3']
assert cfg.val1.val2 == ['val1', 'val2', 'val3']
assert cfg.val1.val3 == ['yes', 'yes', 'yes']
assert cfg.val1.val5 == ['val10', 'val11', None]
#
cfg.val1.val1.pop(2)
assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val1', 'val2']
assert cfg.val1.val3 == ['yes', 'yes']
assert cfg.val1.val5 == ['val10', 'val11']
#
cfg.val1.val2 = ['val2', 'val2']
cfg.val1.val3 = ['val2', 'val2']
cfg.val1.val5 = ['val2', 'val2']
assert cfg.val1.val2 == ['val2', 'val2']
assert cfg.val1.val3 == ['val2', 'val2']
assert cfg.val1.val5 == ['val2', 'val2']
#
cfg.val1.val1.append('val3')
assert cfg.val1.val2 == ['val2', 'val2', 'val3']
assert cfg.val1.val3 == ['val2', 'val2', 'yes']
assert cfg.val1.val5 == ['val2', 'val2', None]
cfg.cfgimpl_get_settings().remove('cache')
cfg.val4 = ['val10', 'val11', 'val12']
#if value is already set, not updated !
cfg.val1.val1.pop(2)
cfg.val1.val1.append('val3')
cfg.val1.val1 = ['val1', 'val2', 'val3']
assert cfg.val1.val5 == ['val2', 'val2', 'val12']
def test_callback_hidden():
opt1 = BoolOption('opt1', '')
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': (('od1.opt1', False),)})
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
od1 = OptionDescription('od1', '', [opt1], properties=('hidden',))
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
@ -498,3 +594,84 @@ def test_callback_hidden():
cfg.read_write()
raises(PropertiesOptionError, 'cfg.od1.opt1')
cfg.od2.opt2
def test_callback_two_disabled():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',))
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_write()
raises(PropertiesOptionError, 'cfg.od2.opt2')
def test_callback_calculating_disabled():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_write()
raises(ConfigError, 'cfg.od2.opt2')
def test_callback_calculating_mandatory():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',))
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_only()
raises(ConfigError, 'cfg.od2.opt2')
def test_callback_two_disabled_multi():
opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True)
od1 = OptionDescription('od1', '', [opt1])
od2 = OptionDescription('od2', '', [opt2])
maconfig = OptionDescription('rootconfig', '', [od1, od2])
cfg = Config(maconfig)
cfg.read_write()
raises(PropertiesOptionError, 'cfg.od2.opt2')
def test_callback_multi_list_params():
val1 = StrOption('val1', "", multi=True, default=['val1', 'val2'])
val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'': ((val1, False),)})
oval2 = OptionDescription('val2', '', [val2])
maconfig = OptionDescription('rootconfig', '', [val1, oval2])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val2.val2 == ['val', 'val', 'val', 'val']
def test_callback_multi_list_params_key():
val1 = StrOption('val1', "", multi=True, default=['val1', 'val2'])
val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'value': ((val1, False),)})
oval2 = OptionDescription('val2', '', [val2])
maconfig = OptionDescription('rootconfig', '', [val1, oval2])
cfg = Config(maconfig)
cfg.read_write()
assert cfg.val2.val2 == ['val', 'val', 'val', 'val']
def test_callback_multi_multi():
val1 = StrOption('val1', "", multi=True, default=['val1', 'val2', 'val3'])
val2 = StrOption('val2', "", multi=True, default=['val11', 'val12'])
val3 = StrOption('val3', "", default='val4')
val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val2, False))})
val5 = StrOption('val5', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val3, False))})
val6 = StrOption('val6', "", multi=True, default=['val21', 'val22', 'val23'])
val7 = StrOption('val7', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val6, False))})
raises(ValueError, "StrOption('val8', '', multi=True, callback=return_list2, callback_params={'value': ((val1, False), (val6, False))})")
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5, val6, val7])
cfg = Config(maconfig)
cfg.read_write()
raises(ConfigError, "cfg.val4")
assert cfg.val5 == ['val1', 'val4', 'val2', 'val4', 'val3', 'val4']
assert cfg.val7 == ['val1', 'val21', 'val2', 'val22', 'val3', 'val23']

View File

@ -4,7 +4,7 @@ from py.test import raises
from tiramisu.setting import owners, groups
from tiramisu.config import Config
from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
OptionDescription
SymLinkOption, OptionDescription
def test_consistency_not_equal():
@ -22,6 +22,16 @@ def test_consistency_not_equal():
c.b = 2
def test_consistency_not_equal_symlink():
a = IntOption('a', '')
b = IntOption('b', '')
c = SymLinkOption('c', a)
od = OptionDescription('od', '', [a, b, c])
a.impl_add_consistency('not_equal', b)
c = Config(od)
assert set(od._consistencies.keys()) == set([a, b])
def test_consistency_not_equal_multi():
a = IntOption('a', '', multi=True)
b = IntOption('b', '', multi=True)

View File

@ -327,30 +327,29 @@ def test_reset_properties():
cfg = Config(descr)
setting = cfg.cfgimpl_get_settings()
option = cfg.cfgimpl_get_description().gc.dummy
assert setting._p_.get_properties(cfg) == {}
assert setting._p_.get_modified_properties() == {}
setting.append('frozen')
assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))}
assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'cache', 'validator'))}
setting.reset()
assert setting._p_.get_properties(cfg) == {}
assert setting._p_.get_modified_properties() == {}
setting[option].append('test')
assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))}
assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))}
setting.reset()
assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))}
assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))}
setting.append('frozen')
assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))}
assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))}
setting.reset(option)
assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))}
assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache'))}
setting[option].append('test')
assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))}
assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))}
setting.reset(all_properties=True)
assert setting._p_.get_properties(cfg) == {}
assert setting._p_.get_modified_properties() == {}
raises(ValueError, 'setting.reset(all_properties=True, opt=option)')
a = descr.wantref
setting[a].append('test')
setting[a].reset()
def test_reset_multiple():
descr = make_description()
cfg = Config(descr)

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

@ -64,9 +64,9 @@ def test_make_dict_filter():
config = Config(descr)
config.read_write()
subresult = {'numero_etab': None, 'nombre_interfaces': 1,
'serveur_ntp': [], 'mode_conteneur_actif': False,
'time_zone': 'Paris', 'nom_machine': 'eoleng',
'activer_proxy_client': False}
'serveur_ntp': [], 'mode_conteneur_actif': False,
'time_zone': 'Paris', 'nom_machine': 'eoleng',
'activer_proxy_client': False}
result = {}
for key, value in subresult.items():
result['general.' + key] = value
@ -114,7 +114,6 @@ def test_iter_not_group():
raises(TypeError, "list(config.iter_groups(group_type='family'))")
def test_groups_with_master():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
@ -252,6 +251,22 @@ def test_values_with_master_and_slaves_master():
assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
def test_values_with_master_and_slaves_master_error():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
interface1.impl_set_group_type(groups.master)
maconfig = OptionDescription('toto', '', [interface1])
cfg = Config(maconfig)
cfg.read_write()
cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"]
raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']
raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
def test_values_with_master_owner():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)

View File

@ -88,6 +88,64 @@ def test_multiple_requires():
c.ip_address_service
def test_multiple_requires_cumulative():
a = StrOption('activate_service', '')
b = IPOption('ip_address_service', '',
requires=[{'option': a, 'expected': 'yes', 'action': 'disabled'},
{'option': a, 'expected': 'yes', 'action': 'hidden'}])
od = OptionDescription('service', '', [a, b])
c = Config(od)
c.read_write()
c.ip_address_service
c.activate_service = 'yes'
props = []
try:
c.ip_address_service
except PropertiesOptionError as err:
props = err.proptype
assert set(props) == set(['hidden', 'disabled'])
c.activate_service = 'ok'
c.ip_address_service
c.activate_service = 'no'
c.ip_address_service
def test_multiple_requires_cumulative_inverse():
a = StrOption('activate_service', '')
b = IPOption('ip_address_service', '',
requires=[{'option': a, 'expected': 'yes', 'action': 'disabled', 'inverse': True},
{'option': a, 'expected': 'yes', 'action': 'hidden', 'inverse': True}])
od = OptionDescription('service', '', [a, b])
c = Config(od)
c.read_write()
props = []
try:
c.ip_address_service
except PropertiesOptionError as err:
props = err.proptype
assert set(props) == set(['hidden', 'disabled'])
c.activate_service = 'yes'
c.ip_address_service
c.activate_service = 'ok'
props = []
try:
c.ip_address_service
except PropertiesOptionError as err:
props = err.proptype
assert set(props) == set(['hidden', 'disabled'])
c.activate_service = 'no'
props = []
try:
c.ip_address_service
except PropertiesOptionError as err:
props = err.proptype
assert set(props) == set(['hidden', 'disabled'])
def test_multiple_requires_inverse():
a = StrOption('activate_service', '')
b = IPOption('ip_address_service', '',
@ -476,3 +534,9 @@ def test_set_item():
c = Config(od)
c.read_write()
raises(ValueError, 'c.cfgimpl_get_settings()[a] = ("test",)')
def test_properties_conflict():
a = BoolOption('activate_service', '', True)
raises(ValueError, "IPOption('ip_address_service', '', properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])")
raises(ValueError, "od1 = OptionDescription('service', '', [a], properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])")

View File

@ -4,10 +4,13 @@ from py.test import raises
from tiramisu.config import Config, SubConfig
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption, UnicodeOption
StrOption, SymLinkOption, UnicodeOption, IPOption, OptionDescription, \
PortOption, NetworkOption, NetmaskOption, DomainnameOption
def test_slots_option():
c = ChoiceOption('a', '', ('a',))
raises(AttributeError, "c.x = 1")
c = BoolOption('a', '')
raises(AttributeError, "c.x = 1")
c = IntOption('a', '')
@ -20,10 +23,96 @@ def test_slots_option():
raises(AttributeError, "c.x = 1")
c = UnicodeOption('a', '')
raises(AttributeError, "c.x = 1")
c = ChoiceOption('a', '', ('a',))
c = IPOption('a', '')
raises(AttributeError, "c.x = 1")
c = OptionDescription('a', '', [])
raises(AttributeError, "c.x = 1")
c = PortOption('a', '')
raises(AttributeError, "c.x = 1")
c = NetworkOption('a', '')
raises(AttributeError, "c.x = 1")
c = NetmaskOption('a', '')
raises(AttributeError, "c.x = 1")
c = DomainnameOption('a', '')
raises(AttributeError, "c.x = 1")
def test_slots_option_readonly():
a = ChoiceOption('a', '', ('a',))
b = BoolOption('b', '')
c = IntOption('c', '')
d = FloatOption('d', '')
e = StrOption('e', '')
g = UnicodeOption('g', '')
h = IPOption('h', '')
i = PortOption('i', '')
j = NetworkOption('j', '')
k = NetmaskOption('k', '')
l = DomainnameOption('l', '')
m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l])
a._requires = 'a'
b._requires = 'b'
c._requires = 'c'
d._requires = 'd'
e._requires = 'e'
g._requires = 'g'
h._requires = 'h'
i._requires = 'i'
j._requires = 'j'
k._requires = 'k'
l._requires = 'l'
m._requires = 'm'
Config(m)
raises(AttributeError, "a._requires = 'a'")
raises(AttributeError, "b._requires = 'b'")
raises(AttributeError, "c._requires = 'c'")
raises(AttributeError, "d._requires = 'd'")
raises(AttributeError, "e._requires = 'e'")
raises(AttributeError, "g._requires = 'g'")
raises(AttributeError, "h._requires = 'h'")
raises(AttributeError, "i._requires = 'i'")
raises(AttributeError, "j._requires = 'j'")
raises(AttributeError, "k._requires = 'k'")
raises(AttributeError, "l._requires = 'l'")
raises(AttributeError, "m._requires = 'm'")
def test_slots_option_readonly_name():
a = ChoiceOption('a', '', ('a',))
b = BoolOption('b', '')
c = IntOption('c', '')
d = FloatOption('d', '')
e = StrOption('e', '')
f = SymLinkOption('f', c)
g = UnicodeOption('g', '')
h = IPOption('h', '')
i = PortOption('i', '')
j = NetworkOption('j', '')
k = NetmaskOption('k', '')
l = DomainnameOption('l', '')
m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l])
raises(AttributeError, "a._name = 'a'")
raises(AttributeError, "b._name = 'b'")
raises(AttributeError, "c._name = 'c'")
raises(AttributeError, "d._name = 'd'")
raises(AttributeError, "e._name = 'e'")
raises(AttributeError, "f._name = 'f'")
raises(AttributeError, "g._name = 'g'")
raises(AttributeError, "h._name = 'h'")
raises(AttributeError, "i._name = 'i'")
raises(AttributeError, "j._name = 'j'")
raises(AttributeError, "k._name = 'k'")
raises(AttributeError, "l._name = 'l'")
raises(AttributeError, "m._name = 'm'")
def test_slots_description():
# __slots__ for OptionDescription should be complete for __getattr__
slots = set()
for subclass in OptionDescription.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
assert slots == set(OptionDescription.__slots__)
def test_slots_config():

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

View File

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

View File

@ -22,14 +22,15 @@
# ____________________________________________________________
import weakref
from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.option import OptionDescription, Option, SymLinkOption, \
BaseInformation
from tiramisu.setting import groups, Settings, default_encoding, get_storage
from tiramisu.value import Values
from tiramisu.option import OptionDescription, Option, SymLinkOption
from tiramisu.setting import groups, Settings, default_encoding
from tiramisu.storage import get_storages, get_storage, set_storage, \
_impl_getstate_setting
from tiramisu.value import Values, Multi
from tiramisu.i18n import _
class SubConfig(BaseInformation):
class SubConfig(object):
"sub configuration management entry"
__slots__ = ('_impl_context', '_impl_descr', '_impl_path')
@ -252,10 +253,10 @@ class SubConfig(BaseInformation):
:returns: list of matching Option objects
"""
return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
first=False,
type_=type_,
_subpath=self.cfgimpl_get_path()
)
first=False,
type_=type_,
_subpath=self.cfgimpl_get_path()
)
def find_first(self, bytype=None, byname=None, byvalue=None,
type_='option', display_error=True):
@ -293,12 +294,13 @@ class SubConfig(BaseInformation):
return True
try:
value = getattr(self, path)
if value == byvalue:
return True
if isinstance(value, Multi):
return byvalue in value
else:
return value == byvalue
except PropertiesOptionError: # a property is a restriction
# upon the access of the value
pass
return False
return False
def _filter_by_type():
if bytype is None:
@ -323,15 +325,15 @@ class SubConfig(BaseInformation):
continue
if not _filter_by_value():
continue
if not _filter_by_type():
continue
#remove option with propertyerror, ...
if check_properties:
if byvalue is None and check_properties:
try:
value = getattr(self, path)
except PropertiesOptionError:
# a property restricts the access of the value
continue
if not _filter_by_type():
continue
if type_ == 'value':
retval = value
elif type_ == 'path':
@ -501,11 +503,27 @@ class CommonConfig(SubConfig):
def cfgimpl_get_meta(self):
return self._impl_meta
# information
def impl_set_information(self, key, value):
"""updates the information's attribute
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._impl_values.set_information(key, value)
def impl_get_information(self, key, default=None):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
return self._impl_values.get_information(key, default)
# ____________________________________________________________
class Config(CommonConfig):
"main configuration management entry"
__slots__ = ('__weakref__', )
__slots__ = ('__weakref__', '_impl_test')
def __init__(self, descr, session_id=None, persistent=False):
""" Configuration option management master class
@ -520,13 +538,49 @@ class Config(CommonConfig):
:param persistent: if persistent, don't delete storage when leaving
:type persistent: `boolean`
"""
storage = get_storage(self, session_id, persistent)
self._impl_settings = Settings(self, storage)
self._impl_values = Values(self, storage)
settings, values = get_storages(self, session_id, persistent)
self._impl_settings = Settings(self, settings)
self._impl_values = Values(self, values)
super(Config, self).__init__(descr, weakref.ref(self))
self._impl_build_all_paths()
self._impl_meta = None
self._impl_informations = {}
#undocumented option used only in test script
self._impl_test = False
def __getstate__(self):
if self._impl_meta is not None:
raise ConfigError('cannot serialize Config with meta')
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_impl_context', '__weakref__'])
state = {}
for slot in slots:
try:
state[slot] = getattr(self, slot)
except AttributeError:
pass
storage = self._impl_values._p_._storage
if not storage.serializable:
raise ConfigError('this storage is not serialisable, could be a '
'none persistent storage')
state['_storage'] = {'session_id': storage.session_id,
'persistent': storage.persistent}
state['_impl_setting'] = _impl_getstate_setting()
return state
def __setstate__(self, state):
for key, value in state.items():
if key not in ['_storage', '_impl_setting']:
setattr(self, key, value)
set_storage(**state['_impl_setting'])
self._impl_context = weakref.ref(self)
self._impl_settings.context = weakref.ref(self)
self._impl_values.context = weakref.ref(self)
storage = get_storage(test=self._impl_test, **state['_storage'])
self._impl_values._impl_setstate(storage)
self._impl_settings._impl_setstate(storage)
def cfgimpl_reset_cache(self,
only_expired=False,
@ -561,11 +615,10 @@ class Config(CommonConfig):
# child._impl_meta = self
# self._impl_children = children
# storage = get_storage(self, session_id, persistent)
# self._impl_settings = Settings(self, storage)
# self._impl_values = Values(self, storage)
# settings, values = get_storages(self, session_id, persistent)
# self._impl_settings = Settings(self, settings)
# self._impl_values = Values(self, values)
# self._impl_meta = None
# self._impl_informations = {}
# def cfgimpl_get_children(self):
# return self._impl_children

View File

@ -26,7 +26,7 @@ from copy import copy, deepcopy
from types import FunctionType
from IPy import IP
from tiramisu.error import ConflictError
from tiramisu.error import ConflictError, ConfigError
from tiramisu.setting import groups, multitypes
from tiramisu.i18n import _
from tiramisu.autolib import carry_out_calculation
@ -54,10 +54,78 @@ def valid_name(name):
#
class BaseInformation(object):
"interface for an option's information attribute"
__slots__ = ('_impl_informations',)
class BaseOption(object):
"""This abstract base class stands for attribute access
in options that have to be set only once, it is of course done in the
__setattr__ method
"""
__slots__ = ('_name', '_requires', '_properties', '_readonly',
'_consistencies', '_calc_properties', '_impl_informations',
'_state_consistencies', '_state_readonly', '_state_requires',
'_stated')
def __init__(self, name, doc, requires, properties):
if not valid_name(name):
raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name
self._impl_informations = {}
self.impl_set_information('doc', doc)
self._calc_properties, self._requires = validate_requires_arg(
requires, self._name)
self._consistencies = None
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format(
type(properties),
self._name))
if self._calc_properties is not None and properties is not tuple():
set_forbidden_properties = set(properties) & self._calc_properties
if set_forbidden_properties != frozenset():
raise ValueError('conflict: properties already set in '
'requirement {0}'.format(
list(set_forbidden_properties)))
self._properties = properties # 'hidden', 'disabled'...
def __setattr__(self, name, value):
"""set once and only once some attributes in the option,
like `_name`. `_name` cannot be changed one the option and
pushed in the :class:`tiramisu.option.OptionDescription`.
if the attribute `_readonly` is set to `True`, the option is
"frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property)
"""
if not name.startswith('_state') and name not in ('_cache_paths',
'_consistencies'):
is_readonly = False
# never change _name
if name == '_name':
try:
self._name
#so _name is already set
is_readonly = True
except:
pass
try:
if self._readonly is True:
if value is True:
# already readonly and try to re set readonly
# don't raise, just exit
return
is_readonly = True
except AttributeError:
pass
if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format(
self.__class__.__name__,
self._name,
name))
object.__setattr__(self, name, value)
# information
def impl_set_information(self, key, value):
"""updates the information's attribute
(which is a dictionary)
@ -65,47 +133,206 @@ class BaseInformation(object):
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
try:
self._impl_informations[key] = value
except AttributeError:
raise AttributeError(_('{0} has no attribute '
'impl_set_information').format(
self.__class__.__name__))
self._impl_informations[key] = value
def impl_get_information(self, key, default=None):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
try:
if key in self._impl_informations:
return self._impl_informations[key]
elif default is not None:
return default
if key in self._impl_informations:
return self._impl_informations[key]
elif default is not None:
return default
else:
raise ValueError(_("information's item not found: {0}").format(
key))
# serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done.
one of them is the localisation of the options.
The paths are set once for all.
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._consistencies is None:
self._state_consistencies = None
elif load and self._state_consistencies is None:
self._consistencies = None
del(self._state_consistencies)
else:
if load:
consistencies = self._state_consistencies
else:
raise ValueError(_("Information's item"
"not found: {0}").format(key))
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:
raise AttributeError(_('{0} has no attribute '
'impl_get_information').format(
self.__class__.__name__))
pass
def __getstate__(self, stated=True):
"""special method to enable the serialization with pickle
Usualy, a `__getstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter stated: if stated is `True`, the serialization protocol
can be performed, not ready yet otherwise
:parameter type: bool
"""
try:
self._stated
except AttributeError:
raise SystemError(_('cannot serialize Option, '
'only in OptionDescription'))
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_cache_paths', '__weakref__'])
states = {}
for slot in slots:
# remove variable if save variable converted
# in _state_xxxx variable
if '_state' + slot not in slots:
if slot.startswith('_state'):
# should exists
states[slot] = getattr(self, slot)
# remove _state_xxx variable
self.__delattr__(slot)
else:
try:
states[slot] = getattr(self, slot)
except AttributeError:
pass
if not stated:
del(states['_stated'])
return states
# unserialize
def _impl_setstate(self, descr):
"""the under the hood stuff that need to be done
before the serialization.
:type descr: :class:`tiramisu.option.OptionDescription`
"""
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr, load=True)
try:
self._readonly = self._state_readonly
del(self._state_readonly)
del(self._stated)
except AttributeError:
pass
def __setstate__(self, state):
"""special method that enables us to serialize (pickle)
Usualy, a `__setstate__` method does'nt need any parameter,
but somme under the hood stuff need to be done before this action
:parameter state: a dict is passed to the loads, it is the attributes
of the options object
:type state: dict
"""
for key, value in state.items():
setattr(self, key, value)
class Option(BaseInformation):
class Option(BaseOption):
"""
Abstract base class for configuration option's.
Reminder: an Option object is **not** a container for the value
Reminder: an Option object is **not** a container for the value.
"""
__slots__ = ('_name', '_requires', '_multi', '_validator',
'_default_multi', '_default', '_properties', '_callback',
'_multitype', '_master_slaves', '_consistencies',
'_calc_properties', '__weakref__')
__slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_state_callback', '_callback', '_multitype',
'_master_slaves', '__weakref__')
_empty = ''
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
callback_params=None, validator=None, validator_params=None,
properties=None):
"""
:param name: the option's name
@ -120,26 +347,17 @@ class Option(BaseInformation):
:param callback: the name of a function. If set, the function's output
is responsible of the option's value
:param callback_params: the callback's parameter
:param validator: the name of a function wich stands for a custom
:param validator: the name of a function which stands for a custom
validation of the value
:param validator_args: the validator's parameters
:param validator_params: the validator's parameters
:param properties: tuple of default properties
"""
if not valid_name(name):
raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name
self._impl_informations = {}
self.impl_set_information('doc', doc)
self._calc_properties, self._requires = validate_requires_arg(
requires, self._name)
super(Option, self).__init__(name, doc, requires, properties)
self._multi = multi
self._consistencies = None
if validator is not None:
if type(validator) != FunctionType:
raise TypeError(_("validator must be a function"))
if validator_args is None:
validator_args = {}
self._validator = (validator, validator_args)
validate_callback(validator, validator_params, 'validator')
self._validator = (validator, validator_params)
else:
self._validator = None
if not self._multi and default_multi is not None:
@ -161,11 +379,7 @@ class Option(BaseInformation):
"no callback defined"
" yet for option {0}").format(name))
if callback is not None:
if type(callback) != FunctionType:
raise ValueError('callback must be a function')
if callback_params is not None and \
not isinstance(callback_params, dict):
raise ValueError('callback_params must be a dict')
validate_callback(callback, callback_params, 'callback')
self._callback = (callback, callback_params)
else:
self._callback = None
@ -176,14 +390,6 @@ class Option(BaseInformation):
self._default_multi = default_multi
self.impl_validate(default)
self._default = default
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format(
type(properties),
self._name))
self._properties = properties # 'hidden', 'disabled'...
def _launch_consistency(self, func, opt, vals, context, index, opt_):
if context is not None:
@ -240,11 +446,23 @@ class Option(BaseInformation):
def val_validator(val):
if self._validator is not None:
callback_params = deepcopy(self._validator[1])
callback_params.setdefault('', []).insert(0, val)
return carry_out_calculation(self._name, config=context,
callback=self._validator[0],
callback_params=callback_params)
if self._validator[1] is not None:
validator_params = deepcopy(self._validator[1])
if '' in validator_params:
lst = list(validator_params[''])
lst.insert(0, val)
validator_params[''] = tuple(lst)
else:
validator_params[''] = (val,)
else:
validator_params = {'': (val,)}
ret = carry_out_calculation(self._name, config=context,
callback=self._validator[0],
callback_params=validator_params)
if ret not in [False, True]:
raise ConfigError(_('validator should return a boolean, '
'not {0}').format(ret))
return ret
else:
return True
@ -345,6 +563,40 @@ class Option(BaseInformation):
"must be different as {2} option"
"").format(value, self._name, optname))
def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None:
self._state_callback = None
elif load and self._state_callback is None:
self._callback = None
del(self._state_callback)
else:
if load:
callback, callback_params = self._state_callback
else:
callback, callback_params = self._callback
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
self._callback = (callback, cllbck_prms)
else:
self._state_callback = (callback, cllbck_prms)
class ChoiceOption(Option):
"""represents a choice out of several objects.
@ -358,7 +610,7 @@ class ChoiceOption(Option):
def __init__(self, name, doc, values, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, open_values=False, validator=None,
validator_args=None, properties=()):
validator_params=None, properties=()):
"""
:param values: is a list of values the option can possibly take
"""
@ -376,7 +628,7 @@ class ChoiceOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
validator_params=validator_params,
properties=properties)
def impl_get_values(self):
@ -450,10 +702,11 @@ else:
raise ValueError(_('value must be an unicode'))
class SymLinkOption(object):
__slots__ = ('_name', '_opt')
class SymLinkOption(BaseOption):
__slots__ = ('_name', '_opt', '_state_opt')
_opt_type = 'symlink'
_consistencies = None
#not return _opt consistencies
_consistencies = {}
def __init__(self, name, opt):
self._name = name
@ -462,24 +715,41 @@ class SymLinkOption(object):
'must be an option '
'for symlink {0}').format(name))
self._opt = opt
self._readonly = True
def __getattr__(self, name):
if name in ('_name', '_opt', '_consistencies'):
if name in ('_name', '_opt', '_opt_type', '_readonly'):
return object.__getattr__(self, name)
else:
return getattr(self._opt, name)
def _impl_getstate(self, descr):
super(SymLinkOption, self)._impl_getstate(descr)
self._state_opt = descr.impl_get_path_by_opt(self._opt)
def _impl_setstate(self, descr):
self._opt = descr.impl_get_opt_by_path(self._state_opt)
del(self._state_opt)
super(SymLinkOption, self)._impl_setstate(descr)
def _impl_convert_consistencies(self, descr, load=False):
if load:
del(self._state_consistencies)
else:
self._state_consistencies = None
class IPOption(Option):
"represents the choice of an ip"
__slots__ = ('_only_private',)
__slots__ = ('_only_private', '_allow_reserved')
_opt_type = 'ip'
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
properties=None, only_private=False):
callback_params=None, validator=None, validator_params=None,
properties=None, only_private=False, allow_reserved=False):
self._only_private = only_private
self._allow_reserved = allow_reserved
super(IPOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
@ -487,12 +757,12 @@ class IPOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
validator_params=validator_params,
properties=properties)
def _validate(self, value):
ip = IP('{0}/32'.format(value))
if ip.iptype() == 'RESERVED':
if not self._allow_reserved and ip.iptype() == 'RESERVED':
raise ValueError(_("IP mustn't not be in reserved class"))
if self._only_private and not ip.iptype() == 'PRIVATE':
raise ValueError(_("IP must be in private class"))
@ -513,7 +783,7 @@ class PortOption(Option):
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_range=False, allow_zero=False,
allow_wellknown=True, allow_registred=True,
allow_private=False):
@ -547,7 +817,7 @@ class PortOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
validator_params=validator_params,
properties=properties)
def _validate(self, value):
@ -575,7 +845,7 @@ class NetworkOption(Option):
def _validate(self, value):
ip = IP(value)
if ip.iptype() == 'RESERVED':
raise ValueError(_("network mustn't not be in reserved class"))
raise ValueError(_("network shall not be in reserved class"))
class NetmaskOption(Option):
@ -632,7 +902,7 @@ class DomainnameOption(Option):
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
callback_params=None, validator=None, validator_params=None,
properties=None, allow_ip=False, type_='domainname'):
#netbios: for MS domain
#hostname: to identify the device
@ -651,7 +921,7 @@ class DomainnameOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
validator_params=validator_params,
properties=properties)
def _validate(self, value):
@ -674,7 +944,7 @@ class DomainnameOption(Option):
raise ValueError(_("invalid value for {0}, must have dot"
"").format(self._name))
if len(value) > length:
raise ValueError(_("invalid domainname's length for "
raise ValueError(_("invalid domainname's length for"
" {0} (max {1})").format(self._name, length))
if len(value) == 1:
raise ValueError(_("invalid domainname's length for {0} (min 2)"
@ -684,25 +954,23 @@ class DomainnameOption(Option):
raise ValueError(_('invalid domainname'))
class OptionDescription(BaseInformation):
class OptionDescription(BaseOption):
"""Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
"""
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_properties', '_children', '_consistencies',
'_calc_properties', '__weakref__')
'_state_group_type', '_properties', '_children',
'_consistencies', '_calc_properties', '__weakref__',
'_readonly', '_impl_informations', '_state_requires',
'_state_consistencies', '_stated', '_state_readonly')
_opt_type = 'optiondescription'
def __init__(self, name, doc, children, requires=None, properties=None):
"""
:param children: a list of options (including optiondescriptions)
"""
if not valid_name(name):
raise ValueError(_("invalid name: "
" {0} for optiondescription").format(name))
self._name = name
self._impl_informations = {}
self.impl_set_information('doc', doc)
super(OptionDescription, self).__init__(name, doc, requires, properties)
child_names = [child._name for child in children]
#better performance like this
valid_child = copy(child_names)
@ -714,16 +982,7 @@ class OptionDescription(BaseInformation):
'{0}').format(child))
old = child
self._children = (tuple(child_names), tuple(children))
self._calc_properties, self._requires = validate_requires_arg(requires, self._name)
self._cache_paths = None
self._consistencies = None
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format(type(properties),
self._name))
self._properties = properties # 'hidden', 'disabled'...
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default
@ -731,6 +990,8 @@ class OptionDescription(BaseInformation):
return self.impl_get_information('doc')
def __getattr__(self, name):
if name in self.__slots__:
return object.__getattribute__(self, name)
try:
return self._children[1][self._children[0].index(name)]
except ValueError:
@ -767,13 +1028,16 @@ class OptionDescription(BaseInformation):
cache_path=None,
cache_option=None,
_currpath=None,
_consistencies=None):
_consistencies=None,
force_no_consistencies=False):
if _currpath is None and self._cache_paths is not None:
# cache already set
return
if _currpath is None:
save = True
_currpath = []
_consistencies = {}
if not force_no_consistencies:
_consistencies = {}
else:
save = False
if cache_path is None:
@ -781,15 +1045,16 @@ class OptionDescription(BaseInformation):
cache_option = []
for option in self.impl_getchildren():
attr = option._name
if attr.startswith('_cfgimpl'):
continue
if option in cache_option:
raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option)
if not force_no_consistencies:
option._readonly = True
cache_path.append(str('.'.join(_currpath + [attr])))
if not isinstance(option, OptionDescription):
if option._consistencies is not None:
if not force_no_consistencies and \
option._consistencies is not None:
for consistency in option._consistencies:
func, opt = consistency
opts = (option, opt)
@ -802,11 +1067,14 @@ class OptionDescription(BaseInformation):
option.impl_build_cache(cache_path,
cache_option,
_currpath,
_consistencies)
_consistencies,
force_no_consistencies)
_currpath.pop()
if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path))
self._consistencies = _consistencies
if not force_no_consistencies:
self._consistencies = _consistencies
self._readonly = True
def impl_get_opt_by_path(self, path):
try:
@ -870,7 +1138,7 @@ class OptionDescription(BaseInformation):
raise ValueError(_("no child has same nom has master group"
" for: {0}").format(self._name))
else:
raise ValueError(_('group_type : {0}'
raise ValueError(_('group_type: {0}'
' not allowed').format(group_type))
def impl_get_group_type(self):
@ -891,6 +1159,56 @@ class OptionDescription(BaseInformation):
return False
return True
def _impl_getstate(self, descr=None):
"""enables us to export into a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self.impl_build_cache()
descr = self
super(OptionDescription, self)._impl_getstate(descr)
self._state_group_type = str(self._group_type)
for option in self.impl_getchildren():
option._impl_getstate(descr)
def __getstate__(self):
"""special method to enable the serialization with pickle
"""
stated = True
try:
# the `_state` attribute is a flag that which tells us if
# the serialization can be performed
self._stated
except AttributeError:
# if cannot delete, _impl_getstate never launch
# launch it recursivement
# _stated prevent __getstate__ launch more than one time
# _stated is delete, if re-serialize, re-lauch _impl_getstate
self._impl_getstate()
stated = False
return super(OptionDescription, self).__getstate__(stated)
def _impl_setstate(self, descr=None):
"""enables us to import from a dict
:param descr: parent :class:`tiramisu.option.OptionDescription`
"""
if descr is None:
self._cache_paths = None
self.impl_build_cache(force_no_consistencies=True)
descr = self
self._group_type = getattr(groups, self._state_group_type)
del(self._state_group_type)
super(OptionDescription, self)._impl_setstate(descr)
for option in self.impl_getchildren():
option._impl_setstate(descr)
def __setstate__(self, state):
super(OptionDescription, self).__setstate__(state)
try:
self._stated
except AttributeError:
self._impl_setstate()
def validate_requires_arg(requires, name):
"""check malformed requirements
@ -906,6 +1224,8 @@ def validate_requires_arg(requires, name):
ret_requires = {}
config_action = {}
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires:
if not type(require) == dict:
raise ValueError(_("malformed requirements type for option:"
@ -919,6 +1239,7 @@ def validate_requires_arg(requires, name):
'{2}'.format(name,
unknown_keys,
valid_keys))
# prepare all attributes
try:
option = require['option']
expected = require['expected']
@ -967,12 +1288,43 @@ def validate_requires_arg(requires, name):
inverse, transitive, same_action)
else:
ret_requires[action][option][1].append(expected)
# transform dict to tuple
ret = []
for opt_requires in ret_requires.values():
ret_action = []
for require in opt_requires.values():
req = (require[0], tuple(require[1]), require[2], require[3],
require[4], require[5])
ret_action.append(req)
ret_action.append((require[0], tuple(require[1]), require[2],
require[3], require[4], require[5]))
ret.append(tuple(ret_action))
return frozenset(config_action.keys()), tuple(ret)
def validate_callback(callback, callback_params, type_):
if type(callback) != FunctionType:
raise ValueError(_('{0} should be a function').format(type_))
if callback_params is not None:
if not isinstance(callback_params, dict):
raise ValueError(_('{0}_params should be a dict').format(type_))
for key, callbacks in callback_params.items():
if key != '' and len(callbacks) != 1:
raise ValueError(_('{0}_params with key {1} should not have '
'length different to 1').format(type_,
key))
if not isinstance(callbacks, tuple):
raise ValueError(_('{0}_params should be tuple for key "{1}"'
).format(type_, key))
for callbk in callbacks:
if isinstance(callbk, tuple):
option, force_permissive = callbk
if type_ == 'validator' and not force_permissive:
raise ValueError(_('validator not support tuple'))
if not isinstance(option, Option) and not \
isinstance(option, SymLinkOption):
raise ValueError(_('{0}_params should have an option '
'not a {0} for first argument'
).format(type_, type(option)))
if force_permissive not in [True, False]:
raise ValueError(_('{0}_params should have a boolean'
'not a {0} for second argument'
).format(type_, type(
force_permissive)))

View File

@ -24,43 +24,92 @@ from time import time
from copy import copy
import weakref
from tiramisu.error import (RequirementError, PropertiesOptionError,
ConstError, ConfigError)
ConstError)
from tiramisu.i18n import _
"Default encoding for display a Config if raise UnicodeEncodeError"
default_encoding = 'utf-8'
"""If cache and expire is enable, time before cache is expired.
This delay start first time value/setting is set in cache, even if
user access several time to value/setting
"""
expires_time = 5
ro_remove = ('permissive', 'hidden')
ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen',
'mandatory')
rw_remove = ('permissive', 'everything_frozen', 'mandatory')
rw_append = ('frozen', 'disabled', 'validator', 'hidden')
default_properties = ('expire', 'validator')
"""List of default properties (you can add new one if needed).
For common properties and personalise properties, if a propery is set for
an Option and for the Config together, Setting raise a PropertiesOptionError
* Common properties:
hidden
option with this property can only get value in read only mode. This
option is not available in read write mode.
disabled
option with this property cannot be set/get
frozen
cannot set value for option with this properties if 'frozen' is set in
config
mandatory
should set value for option with this properties if 'mandatory' is set in
config
class StorageType:
default_storage = 'dictionary'
storage_type = None
* Special property:
def set_storage(self, name):
if self.storage_type is not None:
raise ConfigError(_('storage_type is already set, cannot rebind it'))
self.storage_type = name
permissive
option with 'permissive' cannot raise PropertiesOptionError for properties
set in permissive
config with 'permissive', whole option in this config cannot raise
PropertiesOptionError for properties set in permissive
def get_storage(self):
if self.storage_type is None:
self.storage_type = self.default_storage
storage = self.storage_type
return 'tiramisu.storage.{0}.storage'.format(
storage)
* Special Config properties:
cache
if set, enable cache settings and values
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:
# ____________________________________________________________
class _NameSpace(object):
"""convenient class that emulates a module
and builds constants (that is, unique names)"""
and builds constants (that is, unique names)
when attribute is added, we cannot delete it
"""
def __setattr__(self, name, value):
if name in self.__dict__:
@ -73,7 +122,6 @@ class _NameSpace:
raise ValueError(name)
# ____________________________________________________________
class GroupModule(_NameSpace):
"emulates a module to manage unique group (OptionDescription) names"
class GroupType(str):
@ -91,21 +139,8 @@ class GroupModule(_NameSpace):
*master* means : groups that have the 'master' attribute set
"""
pass
# setting.groups (emulates a module)
groups = GroupModule()
def populate_groups():
"populates the available groups in the appropriate namespaces"
groups.master = groups.MasterGroupType('master')
groups.default = groups.DefaultGroupType('default')
groups.family = groups.GroupType('family')
# names are in the module now
populate_groups()
# ____________________________________________________________
class OwnerModule(_NameSpace):
"""emulates a module to manage unique owner names.
@ -119,28 +154,6 @@ class OwnerModule(_NameSpace):
class DefaultOwner(Owner):
"""groups that are default (typically 'default')"""
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):
@ -157,18 +170,79 @@ class MultiTypeModule(_NameSpace):
class SlaveMultiType(MultiType):
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():
"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, 'master', multitypes.MasterMultiType('master'))
setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
# ____________________________________________________________
# populate groups, owners and multitypes with default attributes
groups = GroupModule()
populate_groups()
owners = OwnerModule()
populate_owners()
multitypes = MultiTypeModule()
populate_multitypes()
# ____________________________________________________________
class Property(object):
"a property is responsible of the option's value access rules"
__slots__ = ('_setting', '_properties', '_opt', '_path')
@ -191,7 +265,8 @@ class Property(object):
def remove(self, propname):
if propname in self._properties:
self._properties.remove(propname)
self._setting._setproperties(self._properties, self._opt, self._path)
self._setting._setproperties(self._properties, self._opt,
self._path)
def reset(self):
self._setting.reset(_path=self._path)
@ -203,39 +278,6 @@ class Property(object):
return str(list(self._properties))
def set_storage(name, **args):
storage_type.set_storage(name)
settings = __import__(storage_type.get_storage(), globals(), locals(),
['Setting']).Setting()
for option, value in args.items():
try:
getattr(settings, option)
setattr(settings, option, value)
except AttributeError:
raise ValueError(_('option {0} not already exists in storage {1}'
'').format(option, name))
def get_storage(context, session_id, persistent):
def gen_id(config):
return str(id(config)) + str(time())
if session_id is None:
session_id = gen_id(context)
return __import__(storage_type.get_storage(), globals(), locals(),
['Storage']).Storage(session_id, persistent)
def list_sessions():
return __import__(storage_type.get_storage(), globals(), locals(),
['list_sessions']).list_sessions()
def delete_session(session_id):
return __import__(storage_type.get_storage(), globals(), locals(),
['delete_session']).delete_session(session_id)
#____________________________________________________________
class Settings(object):
"``Config()``'s configuration options"
@ -254,9 +296,7 @@ class Settings(object):
# generic owner
self._owner = owners.user
self.context = weakref.ref(context)
import_lib = 'tiramisu.storage.{0}.setting'.format(storage.storage)
self._p_ = __import__(import_lib, globals(), locals(), ['Settings']
).Settings(storage)
self._p_ = storage
#____________________________________________________________
# properties methods
@ -268,7 +308,7 @@ class Settings(object):
return str(list(self._getproperties()))
def __getitem__(self, opt):
path = self._get_opt_path(opt)
path = self._get_path_by_opt(opt)
return self._getitem(opt, path)
def _getitem(self, opt, path):
@ -285,7 +325,7 @@ class Settings(object):
self._p_.reset_all_propertives()
else:
if opt is not None and _path is None:
_path = self._get_opt_path(opt)
_path = self._get_path_by_opt(opt)
self._p_.reset_properties(_path)
self.context().cfgimpl_reset_cache()
@ -297,18 +337,21 @@ class Settings(object):
raise ValueError(_('if opt is not None, path should not be'
' None in _getproperties'))
ntime = None
if self._p_.hascache('property', path):
ntime = time()
is_cached, props = self._p_.getcache('property', path, ntime)
if 'cache' in self and self._p_.hascache(path):
if 'expire' in self:
ntime = int(time())
is_cached, props = self._p_.getcache(path, ntime)
if is_cached:
return props
props = self._p_.getproperties(path, opt._properties)
if is_apply_req:
props |= self.apply_requires(opt, path)
if 'expire' in self:
if ntime is None:
ntime = time()
self._p_.setcache('property', path, props, ntime + expires_time)
if 'cache' in self:
if 'expire' in self:
if ntime is None:
ntime = int(time())
ntime = ntime + expires_time
self._p_.setcache(path, props, ntime)
return props
def append(self, propname):
@ -342,13 +385,17 @@ class Settings(object):
#____________________________________________________________
def validate_properties(self, opt_or_descr, is_descr, is_write, path,
value=None, force_permissive=False,
force_properties=None):
force_properties=None, force_permissives=None):
"""
validation upon the properties related to `opt_or_descr`
:param opt_or_descr: an option or an option description object
:param force_permissive: behaves as if the permissive property
was present
:param force_properties: set() with properties that is force to add
in global properties
:param force_permissives: set() with permissives that is force to add
in global permissives
:param is_descr: we have to know if we are in an option description,
just because the mandatory property
doesn't exist here
@ -365,6 +412,8 @@ class Settings(object):
self_properties = copy(self._getproperties())
if force_permissive is True or 'permissive' in self_properties:
properties -= self._p_.getpermissive()
if force_permissives is not None:
properties -= force_permissives
# global properties
if force_properties is not None:
@ -402,15 +451,15 @@ class Settings(object):
def setpermissive(self, permissive, opt=None, path=None):
"""
enables us to put the permissives in the storage
:param path: the option's path
:param path: the option's path
:param type: str
:param opt: if an option object is set, the path is extracted.
it is better (faster) to set the path parameter
:param opt: if an option object is set, the path is extracted.
it is better (faster) to set the path parameter
instead of passing a :class:`tiramisu.option.Option()` object.
"""
if opt is not None and path is None:
path = self._get_opt_path(opt)
path = self._get_path_by_opt(opt)
if not isinstance(permissive, tuple):
raise TypeError(_('permissive must be a tuple'))
self._p_.setpermissive(path, permissive)
@ -433,18 +482,23 @@ class Settings(object):
self.append(prop)
def read_only(self):
"convenience method to freeze, hidde and disable"
"convenience method to freeze, hide and disable"
self._read(ro_remove, ro_append)
def read_write(self):
"convenience method to freeze, hidde and disable"
"convenience method to freeze, hide and disable"
self._read(rw_remove, rw_append)
def reset_cache(self, only_expired):
"""reset all settings in cache
:param only_expired: if True reset only expired cached values
:type only_expired: boolean
"""
if only_expired:
self._p_.reset_expired_cache('property', time())
self._p_.reset_expired_cache(int(time()))
else:
self._p_.reset_all_cache('property')
self._p_.reset_all_cache()
def apply_requires(self, opt, path):
"""carries out the jit (just in time) requirements between options
@ -456,12 +510,13 @@ class Settings(object):
let's have a look at all the tuple's items:
- **option** is the target option's name or path
- **option** is the target option's
- **expected** is the target option's value that is going to trigger an action
- **expected** is the target option's value that is going to trigger
an action
- **action** is the (property) action to be accomplished if the target option
happens to have the expected value
- **action** is the (property) action to be accomplished if the target
option happens to have the expected value
- if **inverse** is `True` and if the target option's value does not
apply, then the property action must be removed from the option's
@ -498,7 +553,7 @@ class Settings(object):
for require in requires:
option, expected, action, inverse, \
transitive, same_action = require
reqpath = self._get_opt_path(option)
reqpath = self._get_path_by_opt(option)
if reqpath == path or reqpath.startswith(path + '.'):
raise RequirementError(_("malformed requirements "
"imbrication detected for option:"
@ -527,7 +582,32 @@ class Settings(object):
calc_properties.add(action)
# the calculation cannot be carried out
break
return calc_properties
return calc_properties
def _get_opt_path(self, opt):
def _get_path_by_opt(self, opt):
"""just a wrapper to get path in optiondescription's cache
:param opt: `Option`'s object
:returns: path
"""
return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
def get_modified_properties(self):
return self._p_.get_modified_properties()
def get_modified_permissives(self):
return self._p_.get_modified_permissives()
def __getstate__(self):
return {'_p_': self._p_, '_owner': str(self._owner)}
def _impl_setstate(self, storage):
self._p_._storage = storage
def __setstate__(self, states):
self._p_ = states['_p_']
try:
self._owner = getattr(owners, states['_owner'])
except AttributeError:
owners.addowner(states['_owner'])
self._owner = getattr(owners, states['_owner'])

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
#
# ____________________________________________________________
from tiramisu.storage.dictionary.storage import Cache
from ..util import Cache
class Settings(Cache):
@ -50,12 +50,21 @@ class Settings(Cache):
except KeyError:
pass
def get_properties(self, context):
return self._properties
# permissive
def setpermissive(self, path, permissive):
self._permissives[path] = frozenset(permissive)
def getpermissive(self, path=None):
return self._permissives.get(path, frozenset())
def get_modified_properties(self):
"""return all modified settings in a dictionary
example: {'path1': set(['prop1', 'prop2'])}
"""
return self._properties
def get_modified_permissives(self):
"""return all modified permissives in a dictionary
example: {'path1': set(['perm1', 'perm2'])}
"""
return self._permissives

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"default plugin for cache: set it in a simple dictionary"
# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software; you can redistribute it and/or modify
@ -19,9 +18,12 @@
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.error import ConfigError
from ..util import SerializeObject
class Setting(object):
class Setting(SerializeObject):
"""Dictionary storage has no particular setting.
"""
pass
@ -38,15 +40,18 @@ def delete_session(session_id):
class Storage(object):
__slots__ = ('session_id',)
__slots__ = ('session_id', 'persistent')
storage = 'dictionary'
#if object could be serializable
serializable = True
def __init__(self, session_id, persistent):
if session_id in _list_sessions:
def __init__(self, session_id, persistent, test=False):
if not test and session_id in _list_sessions:
raise ValueError(_('session already used'))
if persistent:
raise ValueError(_('a dictionary cannot be persistent'))
self.session_id = session_id
self.persistent = persistent
_list_sessions.append(self.session_id)
def __del__(self):
@ -54,45 +59,3 @@ class Storage(object):
_list_sessions.remove(self.session_id)
except AttributeError:
pass
class Cache(object):
__slots__ = ('_cache', 'storage')
key_is_path = False
def __init__(self, storage):
self._cache = {}
self.storage = storage
def setcache(self, cache_type, path, val, time):
self._cache[path] = (val, time)
def getcache(self, cache_type, path, exp):
value, created = self._cache[path]
if exp < created:
return True, value
return False, None
def hascache(self, cache_type, path):
""" path is in the cache
:param cache_type: value | property
:param path: the path's option
"""
return path in self._cache
def reset_expired_cache(self, cache_type, exp):
for key in tuple(self._cache.keys()):
val, created = self._cache[key]
if exp > created:
del(self._cache[key])
def reset_all_cache(self, cache_type):
"empty the cache"
self._cache.clear()
def get_cached(self, cache_type, context):
"""return all values in a dictionary
example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')}
"""
return self._cache

View File

@ -18,16 +18,17 @@
#
# ____________________________________________________________
from tiramisu.storage.dictionary.storage import Cache
from ..util import Cache
class Values(Cache):
__slots__ = ('_values', '__weakref__')
__slots__ = ('_values', '_informations', '__weakref__')
def __init__(self, storage):
"""init plugin means create values storage
"""
self._values = {}
self._informations = {}
# should init cache too
super(Values, self).__init__(storage)
@ -72,3 +73,22 @@ class Values(Cache):
return: owner object
"""
return self._values.get(path, (default, None))[0]
def set_information(self, key, value):
"""updates the information's attribute
(which is a dictionary)
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._informations[key] = value
def get_information(self, key):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
if key in self._informations:
return self._informations[key]
else:
raise ValueError("not found")

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
#
# ____________________________________________________________
from tiramisu.storage.sqlite3.storage import Cache
from .sqlite3db import Sqlite3DB
class Settings(Cache):
class Settings(Sqlite3DB):
__slots__ = tuple()
def __init__(self, storage):
@ -29,23 +29,23 @@ class Settings(Cache):
permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path text '
permissives_table += 'primary key, permissives text)'
# should init cache too
super(Settings, self).__init__('property', storage)
self.storage.execute(settings_table, commit=False)
self.storage.execute(permissives_table)
super(Settings, self).__init__(storage)
self._storage.execute(settings_table, commit=False)
self._storage.execute(permissives_table)
# propertives
def setproperties(self, path, properties):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE path = ?", (path,),
False)
self.storage.execute("INSERT INTO property(path, properties) VALUES "
"(?, ?)", (path,
self._sqlite_encode(properties)))
self._storage.execute("DELETE FROM property WHERE path = ?", (path,),
False)
self._storage.execute("INSERT INTO property(path, properties) VALUES "
"(?, ?)", (path,
self._sqlite_encode(properties)))
def getproperties(self, path, default_properties):
path = self._sqlite_encode_path(path)
value = self.storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,))
value = self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,))
if value is None:
return set(default_properties)
else:
@ -53,42 +53,53 @@ class Settings(Cache):
def hasproperties(self, path):
path = self._sqlite_encode_path(path)
return self.storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) is not None
return self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) is not None
def reset_all_propertives(self):
self.storage.execute("DELETE FROM property")
self._storage.execute("DELETE FROM property")
def reset_properties(self, path):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE path = ?", (path,))
def get_properties(self, context):
"""return all properties in a dictionary
"""
ret = {}
for path, properties in self.storage.select("SELECT * FROM property",
only_one=False):
path = self._sqlite_decode_path(path)
properties = self._sqlite_decode(properties)
ret[path] = properties
return ret
self._storage.execute("DELETE FROM property WHERE path = ?", (path,))
# permissive
def setpermissive(self, path, permissive):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
False)
self.storage.execute("INSERT INTO permissive(path, permissives) "
"VALUES (?, ?)", (path,
self._sqlite_encode(permissive)
))
self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
False)
self._storage.execute("INSERT INTO permissive(path, permissives) "
"VALUES (?, ?)", (path,
self._sqlite_encode(permissive)
))
def getpermissive(self, path='_none'):
permissives = self.storage.select("SELECT permissives FROM "
"permissive WHERE path = ?",
(path,))
permissives = self._storage.select("SELECT permissives FROM "
"permissive WHERE path = ?",
(path,))
if permissives is None:
return frozenset()
else:
return frozenset(self._sqlite_decode(permissives[0]))
def get_modified_properties(self):
"""return all modified settings in a dictionary
example: {'path1': set(['prop1', 'prop2'])}
"""
ret = {}
for path, properties in self._storage.select("SELECT * FROM property",
only_one=False):
path = self._sqlite_decode_path(path)
ret[path] = self._sqlite_decode(properties)
return ret
def get_modified_permissives(self):
"""return all modified permissives in a dictionary
example: {'path1': set(['perm1', 'perm2'])}
"""
ret = {}
for path, permissives in self._storage.select("SELECT * FROM permissive",
only_one=False):
path = self._sqlite_decode_path(path)
ret[path] = self._sqlite_decode(permissives)
return ret

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.path import basename, splitext, join
import sqlite3
from glob import glob
from ..util import SerializeObject
class Setting(object):
class Setting(SerializeObject):
""":param extension: database file extension (by default: db)
:param dir_database: root database directory (by default: /tmp)
"""
extension = 'db'
dir_database = '/tmp'
@ -50,13 +53,17 @@ def delete_session(session_id):
class Storage(object):
__slots__ = ('_conn', '_cursor', 'persistent', '_session_id')
__slots__ = ('_conn', '_cursor', 'persistent', 'session_id', 'serializable')
storage = 'sqlite3'
def __init__(self, session_id, persistent):
def __init__(self, session_id, persistent, test=False):
self.persistent = persistent
self._session_id = session_id
self._conn = sqlite3.connect(_gen_filename(self._session_id))
if self.persistent:
self.serializable = True
else:
self.serializable = False
self.session_id = session_id
self._conn = sqlite3.connect(_gen_filename(self.session_id))
self._conn.text_factory = str
self._cursor = self._conn.cursor()
@ -78,82 +85,4 @@ class Storage(object):
self._cursor.close()
self._conn.close()
if not self.persistent:
delete_session(self._session_id)
class Cache(object):
__slots__ = ('storage',)
key_is_path = True
def __init__(self, cache_type, storage):
self.storage = storage
cache_table = 'CREATE TABLE IF NOT EXISTS cache_{0}(path '.format(
cache_type)
cache_table += 'text primary key, value text, time real)'
self.storage.execute(cache_table)
# value
def _sqlite_decode_path(self, path):
if path == '_none':
return None
else:
return path
def _sqlite_encode_path(self, path):
if path is None:
return '_none'
else:
return path
def _sqlite_decode(self, value):
return loads(value)
def _sqlite_encode(self, value):
if isinstance(value, list):
value = list(value)
return dumps(value)
def setcache(self, cache_type, path, val, time):
convert_value = self._sqlite_encode(val)
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM cache_{0} WHERE path = ?".format(
cache_type), (path,), False)
self.storage.execute("INSERT INTO cache_{0}(path, value, time) "
"VALUES (?, ?, ?)".format(cache_type),
(path, convert_value, time))
def getcache(self, cache_type, path, exp):
path = self._sqlite_encode_path(path)
cached = self.storage.select("SELECT value FROM cache_{0} WHERE "
"path = ? AND time >= ?".format(
cache_type), (path, exp))
if cached is None:
return False, None
else:
return True, self._sqlite_decode(cached[0])
def hascache(self, cache_type, path):
path = self._sqlite_encode_path(path)
return self.storage.select("SELECT value FROM cache_{0} WHERE "
"path = ?".format(cache_type),
(path,)) is not None
def reset_expired_cache(self, cache_type, exp):
self.storage.execute("DELETE FROM cache_{0} WHERE time < ?".format(
cache_type), (exp,))
def reset_all_cache(self, cache_type):
self.storage.execute("DELETE FROM cache_{0}".format(cache_type))
def get_cached(self, cache_type, context):
"""return all values in a dictionary
example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')}
"""
ret = {}
for path, value, time in self.storage.select("SELECT * FROM cache_{0}"
"".format(cache_type),
only_one=False):
path = self._sqlite_decode_path(path)
value = self._sqlite_decode(value)
ret[path] = (value, time)
return ret
delete_session(self.session_id)

View File

@ -18,22 +18,25 @@
#
# ____________________________________________________________
from tiramisu.storage.sqlite3.storage import Cache
from .sqlite3db import Sqlite3DB
from tiramisu.setting import owners
class Values(Cache):
class Values(Sqlite3DB):
__slots__ = ('__weakref__',)
def __init__(self, storage):
"""init plugin means create values storage
"""
# should init cache too
super(Values, self).__init__('value', storage)
super(Values, self).__init__(storage)
values_table = 'CREATE TABLE IF NOT EXISTS value(path text primary '
values_table += 'key, value text, owner text)'
self.storage.execute(values_table)
for owner in self.storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
self._storage.execute(values_table, commit=False)
informations_table = 'CREATE TABLE IF NOT EXISTS information(key text primary '
informations_table += 'key, value text)'
self._storage.execute(informations_table)
for owner in self._storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
try:
getattr(owners, owner[0])
except AttributeError:
@ -41,7 +44,7 @@ class Values(Cache):
# sqlite
def _sqlite_select(self, path):
return self.storage.select("SELECT value FROM value WHERE path = ?",
return self._storage.select("SELECT value FROM value WHERE path = ?",
(path,))
# value
@ -51,7 +54,7 @@ class Values(Cache):
"""
self.resetvalue(path)
path = self._sqlite_encode_path(path)
self.storage.execute("INSERT INTO value(path, value, owner) VALUES "
self._storage.execute("INSERT INTO value(path, value, owner) VALUES "
"(?, ?, ?)", (path, self._sqlite_encode(value),
str(owner)))
@ -73,14 +76,14 @@ class Values(Cache):
"""remove value means delete value in storage
"""
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM value WHERE path = ?", (path,))
self._storage.execute("DELETE FROM value WHERE path = ?", (path,))
def get_modified_values(self):
"""return all values in a dictionary
example: {option1: (owner, 'value1'), option2: (owner, 'value2')}
"""
ret = {}
for path, value, owner in self.storage.select("SELECT * FROM value",
for path, value, owner in self._storage.select("SELECT * FROM value",
only_one=False):
path = self._sqlite_decode_path(path)
owner = getattr(owners, owner)
@ -94,7 +97,7 @@ class Values(Cache):
"""change owner for an option
"""
path = self._sqlite_encode_path(path)
self.storage.execute("UPDATE value SET owner = ? WHERE path = ?",
self._storage.execute("UPDATE value SET owner = ? WHERE path = ?",
(str(owner), path))
def getowner(self, path, default):
@ -102,7 +105,7 @@ class Values(Cache):
return: owner object
"""
path = self._sqlite_encode_path(path)
owner = self.storage.select("SELECT owner FROM value WHERE path = ?",
owner = self._storage.select("SELECT owner FROM value WHERE path = ?",
(path,))
if owner is None:
return default
@ -114,3 +117,27 @@ class Values(Cache):
except AttributeError:
owners.addowner(owner)
return getattr(owners, owner)
def set_information(self, key, value):
"""updates the information's attribute
(which is a dictionary)
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._storage.execute("DELETE FROM information WHERE key = ?", (key,),
False)
self._storage.execute("INSERT INTO information(key, value) VALUES "
"(?, ?)", (key, self._sqlite_encode(value)))
def get_information(self, key):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
value = self._storage.select("SELECT value FROM information WHERE key = ?",
(key,))
if value is None:
raise ValueError("not found")
else:
return self._sqlite_decode(value[0])

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)
# the storage type is dictionary or sqlite3
import_lib = 'tiramisu.storage.{0}.value'.format(storage.storage)
self._p_ = __import__(import_lib, globals(), locals(), ['Values'],
).Values(storage)
self._p_ = storage
def _getdefault(self, opt):
"""
@ -126,7 +124,7 @@ class Values(object):
return True
return False
def _getcallback_value(self, opt, index=None):
def _getcallback_value(self, opt, index=None, max_len=None):
"""
retrieves a value for the options that have a callback
@ -141,7 +139,7 @@ class Values(object):
return carry_out_calculation(opt._name, config=self.context(),
callback=callback,
callback_params=callback_params,
index=index)
index=index, max_len=max_len)
def __getitem__(self, opt):
"enables us to use the pythonic dictionary-like access to values"
@ -149,25 +147,28 @@ class Values(object):
def getitem(self, opt, path=None, validate=True, force_permissive=False,
force_properties=None, validate_properties=True):
ntime = None
if path is None:
path = self._get_opt_path(opt)
if self._p_.hascache('value', path):
ntime = time()
is_cached, value = self._p_.getcache('value', path, ntime)
ntime = None
setting = self.context().cfgimpl_get_settings()
if 'cache' in setting and self._p_.hascache(path):
if 'expire' in setting:
ntime = int(time())
is_cached, value = self._p_.getcache(path, ntime)
if is_cached:
if opt.impl_is_multi() and not isinstance(value, Multi):
#load value so don't need to validate if is not a Multi
value = Multi(value, self.context, opt, path, validate=False)
return value
val = self._getitem(opt, path, validate, force_permissive, force_properties,
validate_properties)
if 'expire' in self.context().cfgimpl_get_settings() and validate and \
validate_properties and force_permissive is False and \
force_properties is None:
if ntime is None:
ntime = time()
self._p_.setcache('value', path, val, ntime + expires_time)
val = self._getitem(opt, path, validate, force_permissive,
force_properties, validate_properties)
if 'cache' in setting and validate and validate_properties and \
force_permissive is False and force_properties is None:
if 'expire' in setting:
if ntime is None:
ntime = int(time())
ntime = ntime + expires_time
self._p_.setcache(path, val, ntime)
return val
@ -176,11 +177,20 @@ class Values(object):
# options with callbacks
setting = self.context().cfgimpl_get_settings()
is_frozen = 'frozen' in setting[opt]
# For calculating properties, we need value (ie for mandatory value).
# If value is calculating with a PropertiesOptionError's option
# _getcallback_value raise a ConfigError.
# We can not raise ConfigError if this option should raise
# PropertiesOptionError too. So we get config_error and raise
# ConfigError if properties did not raise.
config_error = None
force_permissives = None
# if value is callback and is not set
# or frozen with force_default_on_freeze
if opt.impl_has_callback() and (
self._is_default_owner(path) or
(is_frozen and 'force_default_on_freeze' in setting[opt])):
lenmaster = None
no_value_slave = False
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
@ -192,15 +202,25 @@ class Values(object):
no_value_slave = True
if not no_value_slave:
value = self._getcallback_value(opt)
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
if not isinstance(value, list):
value = [value for i in range(lenmaster)]
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
# suppress value if already set
self.reset(opt, path)
try:
value = self._getcallback_value(opt, max_len=lenmaster)
except ConfigError as err:
# cannot assign config_err directly in python 3.3
config_error = err
value = None
# should not raise PropertiesOptionError if option is
# mandatory
force_permissives = set(['mandatory'])
else:
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
if not isinstance(value, list):
value = [value for i in range(lenmaster)]
if config_error is None:
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
# suppress value if already set
self.reset(opt, path)
# frozen and force default
elif is_frozen and 'force_default_on_freeze' in setting[opt]:
value = self._getdefault(opt)
@ -208,15 +228,18 @@ class Values(object):
value = Multi(value, self.context, opt, path, validate)
else:
value = self._getvalue(opt, path, validate)
if validate:
if config_error is None and validate:
opt.impl_validate(value, self.context(), 'validator' in setting)
if self._is_default_owner(path) and \
if config_error is None and self._is_default_owner(path) and \
'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False)
if validate_properties:
setting.validate_properties(opt, False, False, value=value, path=path,
force_permissive=force_permissive,
force_properties=force_properties)
force_properties=force_properties,
force_permissives=force_permissives)
if config_error is not None:
raise ConfigError(config_error)
return value
def __setitem__(self, opt, value):
@ -230,7 +253,7 @@ class Values(object):
opt.impl_validate(value, self.context(),
'validator' in self.context().cfgimpl_get_settings())
if opt.impl_is_multi() and not isinstance(value, Multi):
value = Multi(value, self.context, opt, path)
value = Multi(value, self.context, opt, path, setitem=True)
self._setvalue(opt, path, value, force_permissive=force_permissive,
is_write=is_write)
@ -302,9 +325,9 @@ class Values(object):
clears the cache if necessary
"""
if only_expired:
self._p_.reset_expired_cache('value', time())
self._p_.reset_expired_cache(int(time()))
else:
self._p_.reset_all_cache('value')
self._p_.reset_all_cache()
def _get_opt_path(self, opt):
"""
@ -315,6 +338,38 @@ class Values(object):
"""
return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
# information
def set_information(self, key, value):
"""updates the information's attribute
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self._p_.set_information(key, value)
def get_information(self, key, default=None):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
try:
return self._p_.get_information(key)
except ValueError:
if default is not None:
return default
else:
raise ValueError(_("information's item"
" not found: {0}").format(key))
def __getstate__(self):
return {'_p_': self._p_}
def _impl_setstate(self, storage):
self._p_._storage = storage
def __setstate__(self, states):
self._p_ = states['_p_']
# ____________________________________________________________
# multi types
@ -324,11 +379,13 @@ class Multi(list):
that support item notation for the values of multi options"""
__slots__ = ('opt', 'path', 'context')
def __init__(self, value, context, opt, path, validate=True):
def __init__(self, value, context, opt, path, validate=True,
setitem=False):
"""
:param value: the Multi wraps a list value
:param context: the home config that has the values
:param opt: the option object that have this Multi value
:param setitem: only if set a value
"""
self.opt = opt
self.path = path
@ -338,27 +395,35 @@ class Multi(list):
if not isinstance(value, list):
value = [value]
if validate and self.opt.impl_get_multitype() == multitypes.slave:
value = self._valid_slave(value)
elif self.opt.impl_get_multitype() == multitypes.master:
value = self._valid_slave(value, setitem)
elif validate and self.opt.impl_get_multitype() == multitypes.master:
self._valid_master(value)
super(Multi, self).__init__(value)
def _valid_slave(self, value):
def _valid_slave(self, value, setitem):
#if slave, had values until master's one
values = self.context().cfgimpl_get_values()
masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
self.opt.impl_get_master_slaves())
mastervalue = getattr(self.context(), masterp)
masterlen = len(mastervalue)
valuelen = len(value)
is_default_owner = not values._is_default_owner(self.path) or setitem
if valuelen > masterlen or (valuelen < masterlen and
not self.context().cfgimpl_get_values(
)._is_default_owner(self.path)):
is_default_owner):
raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format(
self.opt._name, masterp))
elif valuelen < masterlen:
for num in range(0, masterlen - valuelen):
value.append(self.opt.impl_getdefault_multi())
if self.opt.impl_has_callback():
# if callback add a value, but this value will not change
# anymore automaticly (because this value has owner)
index = value.__len__()
value.append(values._getcallback_value(self.opt,
index=index))
else:
value.append(self.opt.impl_getdefault_multi())
#else: same len so do nothing
return value
@ -376,8 +441,17 @@ class Multi(list):
self.opt._name, slave._name))
elif len(value_slave) < masterlen:
for num in range(0, masterlen - len(value_slave)):
value_slave.append(slave.impl_getdefault_multi(),
force=True)
if slave.impl_has_callback():
# if callback add a value, but this value will not
# change anymore automaticly (because this value
# has owner)
index = value_slave.__len__()
value_slave.append(
values._getcallback_value(slave, index=index),
force=True)
else:
value_slave.append(slave.impl_getdefault_multi(),
force=True)
def __setitem__(self, key, value):
self._validate(value)
@ -470,12 +544,15 @@ class Multi(list):
"").format(str(value),
self.opt._name, err))
def pop(self, key, force=False):
def pop(self, index, force=False):
"""the list value can be updated (poped)
only if the option is a master
:param key: index of the element to pop
:return: the requested element
:param index: remove item a index
:type index: int
:param force: force pop item (withoud check master/slave)
:type force: boolean
:returns: item at index
"""
if not force:
if self.opt.impl_get_multitype() == multitypes.slave:
@ -488,8 +565,8 @@ class Multi(list):
#get multi without valid properties
values.getitem(slave,
validate_properties=False
).pop(key, force=True)
).pop(index, force=True)
#set value without valid properties
ret = super(Multi, self).pop(key)
ret = super(Multi, self).pop(index)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
return ret

View File

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-18 15:20+CEST\n"
"POT-Creation-Date: 2013-08-31 09:52+CEST\n"
"PO-Revision-Date: \n"
"Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -11,18 +11,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.5.4\n"
#: tiramisu/autolib.py:49
#: tiramisu/autolib.py:58
msgid "no config specified but needed"
msgstr "aucune config spécifié alors que c'est nécessaire"
#: tiramisu/autolib.py:56
#: tiramisu/autolib.py:65
msgid ""
"unable to carry out a calculation, option {0} has properties: {1} for: {2}"
msgstr ""
"impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : "
"{2}"
#: tiramisu/autolib.py:65
#: tiramisu/autolib.py:74
msgid ""
"unable to carry out a calculation, option value with multi types must have "
"same length for: {0}"
@ -30,86 +30,79 @@ msgstr ""
"impossible d'effectuer le calcul, valeur d'un option avec le type multi doit "
"avoir la même longueur pour : {0}"
#: tiramisu/config.py:45
#: tiramisu/config.py:47
msgid "descr must be an optiondescription, not {0}"
msgstr "descr doit être une optiondescription pas un {0}"
#: tiramisu/config.py:118
#: tiramisu/config.py:121
msgid "unknown group_type: {0}"
msgstr "group_type inconnu: {0}"
#: tiramisu/config.py:154
msgid "no optiondescription for this config (may be metaconfig without meta)"
#: tiramisu/config.py:157
msgid ""
"no option description found for this config (may be metaconfig without meta)"
msgstr ""
"pas d'optiondescription pour cette config (par exemple metaconfig sans meta)"
"pas d'option description pour cette config (peut être une metaconfig sans "
"meta)"
#: tiramisu/config.py:312
msgid "unknown type_ type {0} for _find"
#: tiramisu/config.py:311
msgid "unknown type_ type {0}for _find"
msgstr "type_ type {0} pour _find inconnu"
#: tiramisu/config.py:351
#: tiramisu/config.py:350
msgid "no option found in config with these criteria"
msgstr "aucune option trouvée dans la config avec ces critères"
#: tiramisu/config.py:394
#: tiramisu/config.py:400
msgid "make_dict can't filtering with value without option"
msgstr "make_dict ne peut filtrer sur une valeur mais sans option"
#: tiramisu/config.py:414
#: tiramisu/config.py:421
msgid "unexpected path {0}, should start with {1}"
msgstr "chemin imprévu {0}, devrait commencer par {1}"
#: tiramisu/config.py:527
msgid "metaconfig's children must be a list"
msgstr "enfants d'une metaconfig doit être une liste"
#: tiramisu/config.py:481
msgid "opt in getowner must be an option not {0}"
msgstr "opt dans getowner doit être une option pas {0}"
#: tiramisu/config.py:532
msgid "metaconfig's children must be config, not {0}"
msgstr "enfants d'une metaconfig doit être une config, pas {0}"
#: tiramisu/config.py:537
msgid "all config in metaconfig must have same optiondescription"
msgstr ""
"toutes les configs d'une metaconfig doivent avoir la même optiondescription"
#: tiramisu/config.py:540
msgid "child has already a metaconfig's"
msgstr "enfant a déjà une metaconfig"
#: tiramisu/option.py:70
#: tiramisu/option.py:71
msgid "{0} has no attribute impl_set_information"
msgstr "{0} n'a pas d'attribut impl_set_information"
#: tiramisu/option.py:84
msgid "Information's item not found: {0}"
msgstr "l'élément information non trouvé: {0}"
#: tiramisu/option.py:86
msgid "information's item not found: {0}"
msgstr "aucune config spécifié alors que c'est nécessaire"
#: tiramisu/option.py:89
msgid "{0} has no attribute impl_get_information"
msgstr "{0} n'a pas d'attribut impl_get_information"
#: tiramisu/option.py:124
#: tiramisu/option.py:117
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seul"
#: tiramisu/option.py:159
msgid "invalid name: {0} for option"
msgstr "nom invalide : {0} pour l'option"
#: tiramisu/option.py:134
#: tiramisu/option.py:169
msgid "validator must be a function"
msgstr "validator doit être une fonction"
#: tiramisu/option.py:141
#: tiramisu/option.py:176
msgid "a default_multi is set whereas multi is False in option: {0}"
msgstr ""
"une default_multi est renseigné alors que multi est False dans l'option : {0}"
#: tiramisu/option.py:147
#: tiramisu/option.py:182
msgid "invalid default_multi value {0} for option {1}: {2}"
msgstr "la valeur default_multi est invalide {0} pour l'option {1} : {2}"
#: tiramisu/option.py:150
#: tiramisu/option.py:187
msgid "default value not allowed if option: {0} is calculated"
msgstr "la valeur par défaut n'est pas possible si l'option {0} est calculé"
#: tiramisu/option.py:153
#: tiramisu/option.py:190
msgid ""
"params defined for a callback function but no callback defined yet for "
"option {0}"
@ -117,183 +110,183 @@ msgstr ""
"params définit pour une fonction callback mais par de callback défini encore "
"pour l'option {0}"
#: tiramisu/option.py:174 tiramisu/option.py:718
#: tiramisu/option.py:212 tiramisu/option.py:753
msgid "invalid properties type {0} for {1}, must be a tuple"
msgstr "type des properties invalide {0} pour {1}, doit être un tuple"
#: tiramisu/option.py:273
#: tiramisu/option.py:285
msgid "invalid value {0} for option {1} for object {2}"
msgstr "valeur invalide {0} pour l'option {1} pour l'objet {2}"
#: tiramisu/option.py:278 tiramisu/value.py:368
#: tiramisu/option.py:293 tiramisu/value.py:468
msgid "invalid value {0} for option {1}: {2}"
msgstr "valeur invalide {0} pour l'option {1} : {2}"
#: tiramisu/option.py:290
#: tiramisu/option.py:305
msgid "invalid value {0} for option {1} which must be a list"
msgstr "valeur invalide {0} pour l'option {1} qui doit être une liste"
#: tiramisu/option.py:354
#: tiramisu/option.py:374
msgid "invalid value {0} for option {1} must be different as {2} option"
msgstr ""
"valeur invalide {0} pour l'option {1} doit être différent que l'option {2}"
#: tiramisu/option.py:376
#: tiramisu/option.py:396
msgid "values must be a tuple for {0}"
msgstr "values doit être un tuple pour {0}"
#: tiramisu/option.py:379
#: tiramisu/option.py:399
msgid "open_values must be a boolean for {0}"
msgstr "open_values doit être un booléen pour {0}"
#: tiramisu/option.py:400
#: tiramisu/option.py:420
msgid "value {0} is not permitted, only {1} is allowed"
msgstr "valeur {0} n'est pas permit, seules {1} sont autorisées"
#: tiramisu/option.py:411
#: tiramisu/option.py:432
msgid "value must be a boolean"
msgstr "valeur doit être un booléen"
#: tiramisu/option.py:421
#: tiramisu/option.py:442
msgid "value must be an integer"
msgstr "valeur doit être un numbre"
#: tiramisu/option.py:431
#: tiramisu/option.py:452
msgid "value must be a float"
msgstr "valeur doit être un nombre flottant"
#: tiramisu/option.py:441
msgid "value must be a string"
msgstr "valeur doit être une chaîne"
#: tiramisu/option.py:462
msgid "value must be a string, not {0}"
msgstr "valeur doit être une chaîne, pas {0}"
#: tiramisu/option.py:452
#: tiramisu/option.py:480
msgid "value must be an unicode"
msgstr "valeur doit être une valeur unicode"
#: tiramisu/option.py:463
#: tiramisu/option.py:490
msgid "malformed symlinkoption must be an option for symlink {0}"
msgstr "symlinkoption mal formé doit être une option pour symlink {0}"
#: tiramisu/option.py:497
msgid "IP mustn't not be in reserved class"
#: tiramisu/option.py:526
msgid "IP shall not be in reserved class"
msgstr "IP ne doit pas être d'une classe reservée"
#: tiramisu/option.py:499
#: tiramisu/option.py:528
msgid "IP must be in private class"
msgstr "IP doit être dans la classe privée"
#: tiramisu/option.py:535
#: tiramisu/option.py:566
msgid "inconsistency in allowed range"
msgstr "inconsistence dans la plage autorisée"
#: tiramisu/option.py:540
#: tiramisu/option.py:571
msgid "max value is empty"
msgstr "valeur maximum est vide"
#: tiramisu/option.py:576
msgid "network mustn't not be in reserved class"
#: tiramisu/option.py:608
msgid "network shall not be in reserved class"
msgstr "réseau ne doit pas être dans la classe reservée"
#: tiramisu/option.py:608
#: tiramisu/option.py:640
msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP"
msgstr "réseau invalide {0} ({1}) avec masque {2} ({3}), ce réseau est une IP"
#: tiramisu/option.py:612
#: tiramisu/option.py:645
msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network"
msgstr "IP invalide {0} ({1}) avec masque {2} ({3}), cette IP est un réseau"
#: tiramisu/option.py:617
#: tiramisu/option.py:650
msgid "invalid IP {0} ({1}) with netmask {2} ({3})"
msgstr "IP invalide {0} ({1}) avec masque {2} ({3})"
#: tiramisu/option.py:619
#: tiramisu/option.py:652
msgid "invalid network {0} ({1}) with netmask {2} ({3})"
msgstr "réseau invalide {0} ({1}) avec masque {2} ({3})"
#: tiramisu/option.py:639
#: tiramisu/option.py:672
msgid "unknown type_ {0} for hostname"
msgstr "type_ inconnu {0} pour le nom d'hôte"
#: tiramisu/option.py:642
#: tiramisu/option.py:675
msgid "allow_ip must be a boolean"
msgstr "allow_ip doit être un booléen"
#: tiramisu/option.py:671
#: tiramisu/option.py:704
msgid "invalid value for {0}, must have dot"
msgstr "valeur invalide pour {0}, doit avoir un point"
#: tiramisu/option.py:674
#: tiramisu/option.py:707
msgid "invalid domainname's length for {0} (max {1})"
msgstr "longueur du nom de domaine invalide pour {0} (maximum {1})"
#: tiramisu/option.py:676
#: tiramisu/option.py:710
msgid "invalid domainname's length for {0} (min 2)"
msgstr "longueur du nom de domaine invalide pour {0} (minimum 2)"
#: tiramisu/option.py:680
#: tiramisu/option.py:714
msgid "invalid domainname"
msgstr "nom de domaine invalide"
#: tiramisu/option.py:696
#: tiramisu/option.py:731
msgid "invalid name: {0} for optiondescription"
msgstr "nom invalide : {0} pour l'optiondescription"
#: tiramisu/option.py:707
#: tiramisu/option.py:743
msgid "duplicate option name: {0}"
msgstr "nom de l'option dupliqué : {0}"
#: tiramisu/option.py:731
#: tiramisu/option.py:769
msgid "unknown Option {0} in OptionDescription {1}"
msgstr "Option {} inconnue pour l'OptionDescription{}"
#: tiramisu/option.py:795
#: tiramisu/option.py:820
msgid "duplicate option: {0}"
msgstr "option dupliquée : {0}"
#: tiramisu/option.py:805
#: tiramisu/option.py:850
msgid "no option for path {0}"
msgstr "pas d'option pour le chemin {0}"
#: tiramisu/option.py:811
#: tiramisu/option.py:856
msgid "no option {0} found"
msgstr "pas d'option {0} trouvée"
#: tiramisu/option.py:821
#: tiramisu/option.py:866
msgid "cannot change group_type if already set (old {0}, new {1})"
msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1})"
#: tiramisu/option.py:833
#: tiramisu/option.py:879
msgid "master group {0} shall not have a subgroup"
msgstr "groupe maître {0} ne doit pas avoir de sous-groupe"
#: tiramisu/option.py:836
#: tiramisu/option.py:882
msgid "master group {0} shall not have a symlinkoption"
msgstr "groupe maître {0} ne doit pas avoir de symlinkoption"
#: tiramisu/option.py:839
#: tiramisu/option.py:885
msgid "not allowed option {0} in group {1}: this option is not a multi"
msgstr ""
"option non autorisée {0} dans le groupe {1} : cette option n'est pas une "
"multi"
#: tiramisu/option.py:849
#: tiramisu/option.py:896
msgid "master group with wrong master name for {0}"
msgstr "le groupe maître avec un nom de maître éroné pour {0}"
#: tiramisu/option.py:857
#: tiramisu/option.py:905
msgid "no child has same nom has master group for: {0}"
msgstr "pas d'enfant avec le nom du groupe maître pour {0} "
#: tiramisu/option.py:860
msgid "not allowed group_type : {0}"
msgstr "group_type non autorisé : {0}"
#: tiramisu/option.py:908
msgid "group_type: {0} not allowed"
msgstr "group_type : {0} non autorisé"
#: tiramisu/option.py:889
#: tiramisu/option.py:946
msgid "malformed requirements type for option: {0}, must be a dict"
msgstr ""
"type requirements malformé pour l'option : {0}, doit être un dictionnaire"
#: tiramisu/option.py:905
#: tiramisu/option.py:962
msgid ""
"malformed requirements for option: {0} require must have option, expected "
"and action keys"
@ -301,68 +294,87 @@ msgstr ""
"requirements malformé pour l'option : {0} l'exigence doit avoir les clefs "
"option, exptected et action"
#: tiramisu/option.py:910
#: tiramisu/option.py:967
msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr "requirements malformé pour l'option : {0} inverse doit être un booléen"
#: tiramisu/option.py:914
#: tiramisu/option.py:971
msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr "requirements malformé pour l'option : {0} transitive doit être booléen"
#: tiramisu/option.py:918
#: tiramisu/option.py:975
msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr ""
"requirements malformé pour l'option : {0} same_action doit être un booléen"
#: tiramisu/option.py:923
#: tiramisu/option.py:979
msgid "malformed requirements must be an option in option {0}"
msgstr "requirements malformé doit être une option dans l'option {0}"
#: tiramisu/option.py:926
#: tiramisu/option.py:982
msgid "malformed requirements option {0} should not be a multi"
msgstr "requirements malformé l'option {0} ne doit pas être une multi"
#: tiramisu/option.py:932
#: tiramisu/option.py:988
msgid ""
"malformed requirements second argument must be valid for option {0}: {1}"
msgstr ""
"requirements malformé deuxième argument doit être valide pour l'option {0} : "
"{1}"
#: tiramisu/option.py:936
#: tiramisu/option.py:993
msgid "inconsistency in action types for option: {0} action: {1}"
msgstr "incohérence dans les types action pour l'option : {0} action {1}"
#: tiramisu/setting.py:45
msgid "can't rebind group ({})"
msgstr "ne peut reconsolider un groupe ({0})"
#: tiramisu/setting.py:47
msgid "storage_type is already set, cannot rebind it"
msgstr "storage_type est déjà défini, impossible de le redéfinir"
#: tiramisu/setting.py:50
msgid "can't unbind group ({})"
msgstr "ne peut délier un groupe ({0})"
#: tiramisu/setting.py:67
msgid "can't rebind {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"
msgstr "opt et all_properties ne doit pas être renseigné ensemble dans reset"
#: tiramisu/setting.py:294
#: tiramisu/setting.py:297
msgid "if opt is not None, path should not be None in _getproperties"
msgstr ""
"si opt n'est pas None, path devrait ne pas être à None dans _getproperties"
#: tiramisu/setting.py:391
msgid "cannot change the value for option {0} this option is frozen"
msgstr ""
"ne peut modifié la valeur de l'option {0} cette option n'est pas modifiable"
#: tiramisu/setting.py:298
#: tiramisu/setting.py:397
msgid "trying to access to an option named: {0} with properties {1}"
msgstr "tentative d'accès à une option nommée : {0} avec les propriétés {1}"
#: tiramisu/setting.py:307
#: tiramisu/setting.py:415
msgid "permissive must be a tuple"
msgstr "permissive doit être un tuple"
#: tiramisu/setting.py:314 tiramisu/value.py:208
#: tiramisu/setting.py:422 tiramisu/value.py:277
msgid "invalid generic owner {0}"
msgstr "invalide owner générique {0}"
#: tiramisu/setting.py:367
#: tiramisu/setting.py:503
msgid ""
"malformed requirements imbrication detected for option: '{0}' with "
"requirement on: '{1}'"
@ -370,48 +382,81 @@ msgstr ""
"imbrication de requirements malformé detectée pour l'option : '{0}' avec "
"requirement sur : '{1}'"
#: tiramisu/setting.py:377
#: tiramisu/setting.py:515
msgid "option '{0}' has requirement's property error: {1} {2}"
msgstr "l'option '{0}' a une erreur de propriété pour le requirement : {1} {2}"
#: tiramisu/setting.py:383
msgid "required option not found: {0}"
msgstr "option requise non trouvée : {0}"
#: tiramisu/storage/dictionary/storage.py:37
msgid "dictionary storage cannot delete session"
msgstr ""
"impossible de supprimer une session dans un espace de stockage dictionary"
#: tiramisu/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}"
msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {1}"
#: tiramisu/value.py:270
#: tiramisu/value.py:356
msgid "invalid len for the slave: {0} which has {1} as master"
msgstr "longueur invalide pour une esclave : {0} qui a {1} comme maître"
#: tiramisu/value.py:286
#: tiramisu/value.py:373
msgid "invalid len for the master: {0} which has {1} as slave with greater len"
msgstr ""
"longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus "
"grande longueur"
#: tiramisu/value.py:306
#: tiramisu/value.py:394
msgid "cannot append a value on a multi option {0} which is a slave"
msgstr "ne peut ajouter une valeur sur l'option multi {0} qui est une esclave"
#: tiramisu/value.py:334
#: tiramisu/value.py:429
msgid "cannot sort multi option {0} if master or slave"
msgstr "ne peut trier une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:342
#: tiramisu/value.py:433
msgid "cmp is not permitted in python v3 or greater"
msgstr "cmp n'est pas permis en python v3 ou supérieure"
#: tiramisu/value.py:442
msgid "cannot reverse multi option {0} if master or slave"
msgstr "ne peut inverser une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:350
#: tiramisu/value.py:450
msgid "cannot insert multi option {0} if master or slave"
msgstr "ne peut insérer une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:358
#: tiramisu/value.py:458
msgid "cannot extend multi option {0} if master or slave"
msgstr "ne peut étendre une option multi {0} pour une maître ou une esclave"
#: tiramisu/value.py:381
#: tiramisu/value.py:482
msgid "cannot pop a value on a multi option {0} which is a slave"
msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave"
#~ msgid "metaconfig's children must be a list"
#~ msgstr "enfants d'une metaconfig doit être une liste"
#~ msgid "metaconfig's children must be config, not {0}"
#~ msgstr "enfants d'une metaconfig doit être une config, pas {0}"
#~ msgid "all config in metaconfig must have same optiondescription"
#~ msgstr ""
#~ "toutes les configs d'une metaconfig doivent avoir la même "
#~ "optiondescription"
#~ msgid "child has already a metaconfig's"
#~ msgstr "enfant a déjà une metaconfig"
#~ msgid "not allowed group_type : {0}"
#~ msgstr "group_type non autorisé : {0}"
#~ msgid "required option not found: {0}"
#~ msgstr "option requise non trouvée : {0}"

View File

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