diff --git a/AUTHORS b/AUTHORS index f6e3bb8..1819d02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,3 +6,5 @@ Emmanuel Garette developer Daniel Dehennin contributor Philippe Caseiro contributor + +Gerald Schwartzmann tiramisu's logo (made with The Gimp) diff --git a/Makefile b/Makefile index fce5f8d..e9fabdb 100644 --- a/Makefile +++ b/Makefile @@ -15,19 +15,28 @@ ifneq ($(DESTDIR),) PYTHON_OPTS += --root $(DESTDIR) endif -LAST_TAG := $(shell git describe --tags --abbrev=0) -VERSION := $(shell echo $(LAST_TAG) | awk -F'/' '{print $$2}' || true) -VERSION_FILE := version.in +VERSION := `cat VERSION` + +define gettext + if command -v pygettext >/dev/null 2>&1 ; then \ + P="pygettext" ; \ + else \ + P="pygettext.py" ; \ + fi ; \ + $$P -p translations/ -o $(PACKAGE).pot `find $(PACKAGE)/ -name "*.py"` +endef # Build translation files define build_translation if [ -d ${1} ]; then \ for f in `find ${1} -name "*.po"`; do \ - msgfmt -o `dirname $$f`/`basename -s ".po" $$f`.mo $$f || true; \ + msgfmt -o `dirname $$f`/`basename $$f ".po"`.mo $$f || true; \ done; \ fi endef + + # Install Traduction define install_translation if [ -d ${1} ]; then \ @@ -39,38 +48,33 @@ define install_translation fi endef -all: +all: build-lang clean: $(RM) -r build - $(RM) -r tiramisu.egg-info/ + $(RM) -r $(PACKAGE).egg-info/ $(RM) -r $(TRADUC_DIR)/*/*.mo #test: clean # py.test +# Build or update Portable Object Base Translation for gettext + +build-pot: + $(call gettext) + build-lang: $(call build_translation, $(TRADUC_DIR)) -install-lang: build-lang +install-lang: $(INSTALL_DIR) $(TRADUC_DEST) $(call install_translation, $(TRADUC_DIR)) -install: version.in install-lang +install: install-lang python setup.py install --no-compile $(PYTHON_OPTS) +dist: + git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ HEAD | gzip -9 > $(PACKAGE)-$(VERSION).tar.gz + # List in .PHONY to force generation at each call -version.in: - @if test -n $(VERSION) ; then \ - echo -n $(VERSION) > $(VERSION_FILE) ; \ - fi - @if test ! -s $(VERSION_FILE); then \ - echo -n '0.0-dev' > $(VERSION_FILE); \ - fi - -dist: version.in - git archive --format=tar --prefix $(PACKAGE)-$(VERSION)/ -o $(PACKAGE)-$(VERSION).tar $(LAST_TAG) \ - && tar --xform "s,\(.*\),$(PACKAGE)-$(VERSION)/\1," -f $(PACKAGE)-$(VERSION).tar -r version.in \ - && gzip -9 $(PACKAGE)-$(VERSION).tar - -.PHONY: all clean test install version.in dist +.PHONY: all clean build-pot build-lang install-lang install dist diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0f82de4 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0rc1 diff --git a/doc/api.txt b/doc/api.txt index 1498f14..7fb526f 100644 --- a/doc/api.txt +++ b/doc/api.txt @@ -11,4 +11,5 @@ Auto generated library's API tiramisu.value tiramisu.autolib tiramisu.error + tiramisu.storage diff --git a/doc/config.png b/doc/config.png new file mode 100644 index 0000000..a468275 Binary files /dev/null and b/doc/config.png differ diff --git a/doc/config.svg b/doc/config.svg new file mode 100644 index 0000000..3ff4bc9 --- /dev/null +++ b/doc/config.svg @@ -0,0 +1,257 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + Config + + + + + OptionDescription + + + + Option + + + + Option + + + + OptionDescription + + + + Option + + + + + + + + diff --git a/doc/config.txt b/doc/config.txt index c833172..3c6d9bc 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -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') [, @@ -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. diff --git a/doc/consistency.txt b/doc/consistency.txt index 4b268ed..9de8748 100644 --- a/doc/consistency.txt +++ b/doc/consistency.txt @@ -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' diff --git a/doc/doctest.txt b/doc/doctest.txt index 803260e..d717c29 100644 --- a/doc/doctest.txt +++ b/doc/doctest.txt @@ -23,9 +23,6 @@ option APIs others ---------- -.. automodule:: test.test_config_api - :members: - .. automodule:: test.test_mandatory :members: diff --git a/doc/getting-started.txt b/doc/getting-started.txt index 13cc8f2..a2f6a74 100644 --- a/doc/getting-started.txt +++ b/doc/getting-started.txt @@ -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 diff --git a/doc/index.txt b/doc/index.txt index 561c047..51972bd 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -10,7 +10,7 @@ The tasting of `Tiramisu` ========================= -.. image:: tiramisu.jpeg +.. image:: logo.png :height: 150px `Tiramisu` @@ -34,6 +34,7 @@ controlling options explanations getting-started config option + storage status consistency error diff --git a/doc/logo.png b/doc/logo.png new file mode 100644 index 0000000..084a534 Binary files /dev/null and b/doc/logo.png differ diff --git a/doc/option.txt b/doc/option.txt index 90dea0a..47053b8 100644 --- a/doc/option.txt +++ b/doc/option.txt @@ -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: diff --git a/doc/storage.png b/doc/storage.png new file mode 100644 index 0000000..9bef2b3 Binary files /dev/null and b/doc/storage.png differ diff --git a/doc/storage.svg b/doc/storage.svg new file mode 100644 index 0000000..d710cbc --- /dev/null +++ b/doc/storage.svg @@ -0,0 +1,265 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Config + + + + + Values + + + + Settings + + + + + + + + + + + + Storage + + + Option + + + + diff --git a/doc/storage.txt b/doc/storage.txt new file mode 100644 index 0000000..6bfb18c --- /dev/null +++ b/doc/storage.txt @@ -0,0 +1,52 @@ +Storage +======= + +.. automodule:: tiramisu.storage + +.. automethod:: tiramisu.storage.set_storage + +.. image:: storage.png + +Dictionary +~~~~~~~~~~ + +.. automodule:: tiramisu.storage.dictionary + +Dictionary settings: + +.. automethod:: tiramisu.storage.dictionary.storage.Setting + +Sqlite3 +~~~~~~~ + +.. automodule:: tiramisu.storage.sqlite3 + +Sqlite3 settings: + +.. automethod:: tiramisu.storage.sqlite3.storage.Setting + +Example +~~~~~~~ + +>>> from tiramisu.option import StrOption, OptionDescription +>>> from tiramisu.config import Config +>>> from tiramisu.storage import set_storage +>>> set_storage('sqlite3', dir_database='/tmp/tiramisu') +>>> s = StrOption('str', '') +>>> o = OptionDescription('od', '', [s]) +>>> c1 = Config(o, persistent=True, session_id='xxxx') +>>> c1.str +>>> c1.str = 'yes' +>>> c1.str +'yes' +>>> del(c1) +>>> c2 = Config(o, persistent=True, session_id='xxxx') +>>> c2.str +'yes' +>>> del(c2) +>>> list_sessions() +['xxxx'] +>>> delete_session('xxxx') +>>> c3 = Config(o, persistent=True, session_id='xxxx') +>>> c3.str + diff --git a/doc/tiramisu.jpeg b/doc/tiramisu.jpeg deleted file mode 100644 index 0b5c4ae..0000000 Binary files a/doc/tiramisu.jpeg and /dev/null differ diff --git a/setup.py b/setup.py index 2b57b1c..de30995 100644 --- a/setup.py +++ b/setup.py @@ -1,53 +1,63 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup -from os.path import dirname, abspath, join, normpath, isdir, basename +from os.path import dirname, abspath, join, normpath, isdir from os import listdir -import os -import subprocess def fetch_version(): - """Get version from version.in or latest git tag""" - version_file='version.in' - version = "1.0" - git_last_tag_cmd = ['git', 'describe', '--tags', '--abbrev=0'] + """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 ) diff --git a/test/test_cache.py b/test/test_cache.py index 47270ee..f483c76 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -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) diff --git a/test/test_config.py b/test/test_config.py index 4e38f19..17e863a 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -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(): diff --git a/test/test_config_api.py b/test/test_config_api.py index 62a74c0..ab4b484 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -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) diff --git a/test/test_config_ip.py b/test/test_config_ip.py index e889c92..2bdba53 100644 --- a/test/test_config_ip.py +++ b/test/test_config_ip.py @@ -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]) diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 0266855..8ca8687 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -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'] diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 8dde2a8..5cf53cd 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -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) diff --git a/test/test_option_setting.py b/test/test_option_setting.py index a841b9b..ed5f7d7 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -327,30 +327,29 @@ def test_reset_properties(): cfg = Config(descr) setting = cfg.cfgimpl_get_settings() option = cfg.cfgimpl_get_description().gc.dummy - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} setting.append('frozen') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'cache', 'validator'))} setting.reset() - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} setting[option].append('test') - assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))} setting.reset() - assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))} setting.append('frozen') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} setting.reset(option) - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator'))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache'))} setting[option].append('test') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator')), 'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} setting.reset(all_properties=True) - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} raises(ValueError, 'setting.reset(all_properties=True, opt=option)') a = descr.wantref setting[a].append('test') setting[a].reset() - def test_reset_multiple(): descr = make_description() cfg = Config(descr) diff --git a/test/test_option_validator.py b/test/test_option_validator.py new file mode 100644 index 0000000..8e00916 --- /dev/null +++ b/test/test_option_validator.py @@ -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')") diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 7b9dffe..7ecd860 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -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) diff --git a/test/test_requires.py b/test/test_requires.py index 0f9af61..ba6cf3f 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -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'}])") diff --git a/test/test_slots.py b/test/test_slots.py index 0104e84..1f2aee6 100644 --- a/test/test_slots.py +++ b/test/test_slots.py @@ -4,10 +4,13 @@ from py.test import raises from tiramisu.config import Config, SubConfig from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ - StrOption, OptionDescription, SymLinkOption, UnicodeOption + StrOption, SymLinkOption, UnicodeOption, IPOption, OptionDescription, \ + PortOption, NetworkOption, NetmaskOption, DomainnameOption def test_slots_option(): + c = ChoiceOption('a', '', ('a',)) + raises(AttributeError, "c.x = 1") c = BoolOption('a', '') raises(AttributeError, "c.x = 1") c = IntOption('a', '') @@ -20,10 +23,96 @@ def test_slots_option(): raises(AttributeError, "c.x = 1") c = UnicodeOption('a', '') raises(AttributeError, "c.x = 1") - c = ChoiceOption('a', '', ('a',)) + c = IPOption('a', '') raises(AttributeError, "c.x = 1") c = OptionDescription('a', '', []) raises(AttributeError, "c.x = 1") + c = PortOption('a', '') + raises(AttributeError, "c.x = 1") + c = NetworkOption('a', '') + raises(AttributeError, "c.x = 1") + c = NetmaskOption('a', '') + raises(AttributeError, "c.x = 1") + c = DomainnameOption('a', '') + raises(AttributeError, "c.x = 1") + + +def test_slots_option_readonly(): + a = ChoiceOption('a', '', ('a',)) + b = BoolOption('b', '') + c = IntOption('c', '') + d = FloatOption('d', '') + e = StrOption('e', '') + g = UnicodeOption('g', '') + h = IPOption('h', '') + i = PortOption('i', '') + j = NetworkOption('j', '') + k = NetmaskOption('k', '') + l = DomainnameOption('l', '') + m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l]) + a._requires = 'a' + b._requires = 'b' + c._requires = 'c' + d._requires = 'd' + e._requires = 'e' + g._requires = 'g' + h._requires = 'h' + i._requires = 'i' + j._requires = 'j' + k._requires = 'k' + l._requires = 'l' + m._requires = 'm' + Config(m) + raises(AttributeError, "a._requires = 'a'") + raises(AttributeError, "b._requires = 'b'") + raises(AttributeError, "c._requires = 'c'") + raises(AttributeError, "d._requires = 'd'") + raises(AttributeError, "e._requires = 'e'") + raises(AttributeError, "g._requires = 'g'") + raises(AttributeError, "h._requires = 'h'") + raises(AttributeError, "i._requires = 'i'") + raises(AttributeError, "j._requires = 'j'") + raises(AttributeError, "k._requires = 'k'") + raises(AttributeError, "l._requires = 'l'") + raises(AttributeError, "m._requires = 'm'") + + +def test_slots_option_readonly_name(): + a = ChoiceOption('a', '', ('a',)) + b = BoolOption('b', '') + c = IntOption('c', '') + d = FloatOption('d', '') + e = StrOption('e', '') + f = SymLinkOption('f', c) + g = UnicodeOption('g', '') + h = IPOption('h', '') + i = PortOption('i', '') + j = NetworkOption('j', '') + k = NetmaskOption('k', '') + l = DomainnameOption('l', '') + m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l]) + raises(AttributeError, "a._name = 'a'") + raises(AttributeError, "b._name = 'b'") + raises(AttributeError, "c._name = 'c'") + raises(AttributeError, "d._name = 'd'") + raises(AttributeError, "e._name = 'e'") + raises(AttributeError, "f._name = 'f'") + raises(AttributeError, "g._name = 'g'") + raises(AttributeError, "h._name = 'h'") + raises(AttributeError, "i._name = 'i'") + raises(AttributeError, "j._name = 'j'") + raises(AttributeError, "k._name = 'k'") + raises(AttributeError, "l._name = 'l'") + raises(AttributeError, "m._name = 'm'") + + +def test_slots_description(): + # __slots__ for OptionDescription should be complete for __getattr__ + slots = set() + for subclass in OptionDescription.__mro__: + if subclass is not object: + slots.update(subclass.__slots__) + assert slots == set(OptionDescription.__slots__) def test_slots_config(): diff --git a/test/test_state.py b/test/test_state.py new file mode 100644 index 0000000..8587b4a --- /dev/null +++ b/test/test_state.py @@ -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 diff --git a/test/test_storage.py b/test/test_storage.py index 6dbb721..0b2e3ac 100644 --- a/test/test_storage.py +++ b/test/test_storage.py @@ -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') diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 2cf41ca..de8a8c5 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -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 diff --git a/tiramisu/config.py b/tiramisu/config.py index ae68e98..f1b2851 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -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 diff --git a/tiramisu/option.py b/tiramisu/option.py index 031fb50..10aba5c 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -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))) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 879656c..684ec74 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -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']) diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index e69de29..c232472 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -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) diff --git a/tiramisu/storage/dictionary/__init__.py b/tiramisu/storage/dictionary/__init__.py index e69de29..bc81450 100644 --- a/tiramisu/storage/dictionary/__init__.py +++ b/tiramisu/storage/dictionary/__init__.py @@ -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) diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 580cba3..1b7001b 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -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 diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index d4904c6..465fe26 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -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 diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index 9c3e1f9..fedf1ec 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -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") diff --git a/tiramisu/storage/sqlite3/__init__.py b/tiramisu/storage/sqlite3/__init__.py index e69de29..8d79070 100644 --- a/tiramisu/storage/sqlite3/__init__.py +++ b/tiramisu/storage/sqlite3/__init__.py @@ -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) diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index f91f9fd..ed79181 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -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 diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py new file mode 100644 index 0000000..68f2886 --- /dev/null +++ b/tiramisu/storage/sqlite3/sqlite3db.py @@ -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) diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index d855c88..3b4f265 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -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) diff --git a/tiramisu/storage/sqlite3/value.py b/tiramisu/storage/sqlite3/value.py index 4207c43..672ecab 100644 --- a/tiramisu/storage/sqlite3/value.py +++ b/tiramisu/storage/sqlite3/value.py @@ -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]) diff --git a/tiramisu/storage/util.py b/tiramisu/storage/util.py new file mode 100644 index 0000000..68482e6 --- /dev/null +++ b/tiramisu/storage/util.py @@ -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 diff --git a/tiramisu/value.py b/tiramisu/value.py index d846bc4..d587de1 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -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 diff --git a/translations/fr/tiramisu.po b/translations/fr/tiramisu.po index 7f1c10d..8bceb43 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -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 \n" "Language-Team: LANGUAGE \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}" diff --git a/translations/tiramisu.pot b/translations/tiramisu.pot index faed4f3..ef40426 100644 --- a/translations/tiramisu.pot +++ b/translations/tiramisu.pot @@ -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 \n" "Language-Team: LANGUAGE \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 ""