From da87f40f122444d3320d87fe30e693fbfdc60767 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 3 Nov 2020 22:34:57 +0100 Subject: [PATCH 01/16] do not remove notempty and notunique properties --- tests/test_leadership.py | 38 +++++++++++++++++++++++++++++++++++ tests/test_option_setting.py | 6 +++--- tiramisu/option/baseoption.py | 8 ++------ tiramisu/setting.py | 1 + 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/tests/test_leadership.py b/tests/test_leadership.py index 467bd4d..3b4f2a8 100644 --- a/tests/test_leadership.py +++ b/tests/test_leadership.py @@ -735,6 +735,19 @@ async def test_values_with_leader_and_followers_leader_pop(): assert not await list_sessions() +@pytest.mark.asyncio +async def test_follower_unique(): + 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, properties=('unique',)) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + maconfig = OptionDescription('toto', '', [interface1]) + async with await Config(maconfig) as cfg: + await cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(["192.168.230.145", "192.168.230.146"]) + # unique property is removed for a follower + assert not await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() + assert not await list_sessions() + + @pytest.mark.asyncio async def test_values_with_leader_owner(config_type): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) @@ -976,3 +989,28 @@ async def test_follower_force_store_value_reset(): await cfg.option('od.interface0.ip_admin_eth0').value.reset() assert not await cfg.option('od.interface0.netmask_admin_eth0', 0).owner.isdefault() assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_follower_properties(): + 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, properties=('aproperty',)) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + maconfig = OptionDescription('toto', '', [interface1]) + async with await Config(maconfig) as cfg: + await cfg.property.read_write() + await cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['1.1.1.1', '192.168.0.0']) + await cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty',) + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty',) + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',) + # + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.add('newproperty') + await cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty',) + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty') + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',) + # + await cfg.option('ip_admin_eth0.netmask_admin_eth0').property.add('newproperty1') + await cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty', 'newproperty1') + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty', 'newproperty1') + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty', 'newproperty1') + assert not await list_sessions() diff --git a/tests/test_option_setting.py b/tests/test_option_setting.py index 797e773..197168a 100644 --- a/tests/test_option_setting.py +++ b/tests/test_option_setting.py @@ -191,9 +191,9 @@ async def test_property_get_unique_empty(): async with await Config(descr) as cfg: await cfg.property.read_write() assert await cfg.option('string').property.get() == {'empty', 'unique'} - assert await cfg.option('string2').property.get() == {'empty'} - assert await cfg.option('string3').property.get() == {'unique'} - assert await cfg.option('string4').property.get() == set() + assert await cfg.option('string2').property.get() == {'empty', 'notunique'} + assert await cfg.option('string3').property.get() == {'unique', 'notempty'} + assert await cfg.option('string4').property.get() == {'notunique', 'notempty'} assert not await list_sessions() diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 40a3909..87b37a9 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -72,13 +72,9 @@ class Base: if is_multi: # if option is a multi, it cannot be 'empty' (None not allowed in the list) and cannot have multiple time the same value # 'empty' and 'unique' are removed for follower's option - if 'notunique' in properties: - properties = properties - {'notunique'} - else: + if 'notunique' not in properties: properties = properties | {'unique'} - if 'notempty' in properties: - properties = properties - {'notempty'} - else: + if 'notempty' not in properties: properties = properties | {'empty'} assert isinstance(properties, frozenset), _('invalid properties type {0} for {1},' ' must be a frozenset').format(type(properties), diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 04d3c48..f6abf33 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -117,6 +117,7 @@ FORBIDDEN_SET_PERMISSIVES = frozenset(['force_default_on_freeze', 'force_metaconfig_on_freeze', 'force_store_value']) ALLOWED_LEADER_PROPERTIES = frozenset(['empty', + 'notunique', 'unique', 'force_store_value', 'mandatory', From c9e166f1d43fd44688d31dd4d09b61a6adee1717 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 21 Nov 2020 19:26:50 +0100 Subject: [PATCH 02/16] better error message --- tiramisu/config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tiramisu/config.py b/tiramisu/config.py index d80b4bf..399eeee 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -1267,8 +1267,9 @@ class KernelMixConfig(KernelGroupConfig): apiconfig): config = apiconfig._config_bag.context if config.impl_getname() in [child.impl_getname() for child in self._impl_children]: - raise ConflictError(_('config name must be uniq in ' - 'groupconfig for {0}').format(config.impl_getname())) + raise ConflictError(_('config name "{0}" is not uniq in ' + 'groupconfig "{1}"').format(config.impl_getname(), + self.impl_getname())) config.parents.append(weakref.ref(self)) self._impl_children.append(config) From bfed49a11fb0fd84077b0852d7f9856d7038dc9b Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Mon, 11 Jan 2021 22:40:25 +0100 Subject: [PATCH 03/16] wrong leadership name in dict() --- tests/test_leadership.py | 26 ++++++++++++++++++++++++++ tiramisu/config.py | 5 +++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/test_leadership.py b/tests/test_leadership.py index 3b4f2a8..3f8c34a 100644 --- a/tests/test_leadership.py +++ b/tests/test_leadership.py @@ -298,6 +298,32 @@ async def test_groups_with_leader_make_dict(config_type): assert not await list_sessions() +@pytest.mark.asyncio +async def test_groups_with_leader_make_dict2(config_type): + 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 = Leadership('other', '', [ip_admin_eth0, netmask_admin_eth0]) + od = OptionDescription('root', '', [interface1]) + async with await Config(od) as cfg: + cfg = await get_config(cfg, config_type) + assert await cfg.value.dict() == {'other.ip_admin_eth0': [], 'other.netmask_admin_eth0': []} + assert await cfg.value.dict(leader_to_list=True) == {'other.ip_admin_eth0': []} + if config_type != 'tiramisu-api': + # FIXME useful? already in leadership + assert await cfg.option('other.ip_admin_eth0').value.len() == 0 + assert await cfg.option('other.netmask_admin_eth0').value.len() == 0 + await cfg.option('other.ip_admin_eth0').value.set(['ip1', 'ip2']) + if config_type != 'tiramisu-api': + # FIXME + assert await cfg.option('other.ip_admin_eth0').value.len() == 2 + assert await cfg.option('other.netmask_admin_eth0').value.len() == 2 + assert await cfg.value.dict() == {'other.ip_admin_eth0': ['ip1', 'ip2'], 'other.netmask_admin_eth0': [None, None]} + assert await cfg.value.dict(leader_to_list=True) == {'other.ip_admin_eth0': [{'other.ip_admin_eth0': 'ip1', 'other.netmask_admin_eth0': None}, {'other.ip_admin_eth0': 'ip2', 'other.netmask_admin_eth0': None}]} + if config_type == 'tiramisu-api': + await cfg.send() + assert not await list_sessions() + + @pytest.mark.asyncio async def test_groups_with_leader_default_value(config_type): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) diff --git a/tiramisu/config.py b/tiramisu/config.py index 399eeee..a9bf14d 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -421,7 +421,8 @@ class SubConfig: flatten, fullpath, pathsvalues, - leader_to_list) + leader_to_list, + ) return pathsvalues async def _make_dict(self, @@ -442,7 +443,7 @@ class SubConfig: config_bag) loption_bag.properties = await self.cfgimpl_get_settings().getproperties(loption_bag) leader_pathsvalues = {} - leader_currpath = _currpath + [leader.impl_getname()] + leader_currpath = _currpath + [opt.impl_getname()] await self._make_sub_dict(leader_pathsvalues, leader_currpath, loption_bag, From 99a422dad7654771650bb5f9180c2cc9bfd79c99 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 6 Feb 2021 15:18:28 +0100 Subject: [PATCH 04/16] exected could be a list --- tests/test_requires.py | 32 ++++++++++++++++++++++++++++++++ tiramisu/autolib.py | 3 +-- tiramisu/function.py | 5 ++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tests/test_requires.py b/tests/test_requires.py index 47aa8b8..5df470a 100644 --- a/tests/test_requires.py +++ b/tests/test_requires.py @@ -1070,6 +1070,38 @@ async def test_requires_different_inverse_unicode(config_type): assert not await list_sessions() +@pytest.mark.asyncio +async def test_requires_different_inverse_unicode2(config_type): + a = BoolOption('activate_service', '', False) + d = StrOption('activate_other_service', '', 'val2') + disabled_property = Calculation(calc_value, + Params(ParamValue('disabled'), + kwargs={'condition_0': ParamOption(a), + 'condition_1': ParamOption(d), + 'expected_0': ParamValue(True), + 'expected_1': ParamValue(['val2', 'val3']), + 'condition_operator': ParamValue('OR'), + 'reverse_condition_1': ParamValue(True)})) + b = IPOption('ip_address_service', '', properties=(disabled_property,)) + od = OptionDescription('service', '', [a, d, b]) + async with await Config(od) as cfg: + await cfg.property.read_write() + cfg = await get_config(cfg, config_type) + assert await cfg.option('ip_address_service').value.get() == None + await cfg.option('activate_service').value.set(True) + with pytest.raises(PropertiesOptionError): + await cfg.option('ip_address_service').value.get() + await cfg.option('activate_service').value.set(False) + assert await cfg.option('ip_address_service').value.get() == None + await cfg.option('activate_other_service').value.set('val1') + with pytest.raises(PropertiesOptionError): + await cfg.option('ip_address_service').value.get() + await cfg.option('activate_service').value.set(True) + with pytest.raises(PropertiesOptionError): + await cfg.option('ip_address_service').value.get() + assert not await list_sessions() + + @pytest.mark.asyncio async def test_optiondescription_requires(): a = BoolOption('activate_service', '', True) diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index cfbdb14..71ebf43 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -501,5 +501,4 @@ async def calculate(option, '').format(str(error), callback.__name__, option.impl_get_display_name()) - del error - raise ConfigError(msg) + raise ConfigError(msg) from error diff --git a/tiramisu/function.py b/tiramisu/function.py index 1779bcb..0cd037f 100644 --- a/tiramisu/function.py +++ b/tiramisu/function.py @@ -400,7 +400,10 @@ class CalcValue: for idx, calculated_condition in calculated_conditions.items(): if isinstance(calculated_expected, dict): if idx is not None: - current_matches = calculated_condition == calculated_expected[idx] + if isinstance(calculated_expected[idx], list): + current_matches = calculated_condition in calculated_expected[idx] + else: + current_matches = calculated_condition == calculated_expected[idx] else: current_matches = calculated_condition in calculated_expected.values() else: From 385160cabd3f3712dcfb135a816275ff6f0b72a5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 17 Feb 2021 09:50:36 +0100 Subject: [PATCH 05/16] filename must be a full path --- tests/test_config_api.py | 9 +++------ tiramisu/option/filenameoption.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/test_config_api.py b/tests/test_config_api.py index 2bfa337..27036ee 100644 --- a/tests/test_config_api.py +++ b/tests/test_config_api.py @@ -323,13 +323,10 @@ async def test_filename(config_type): await cfg.option('a').value.set('/tmp') await cfg.option('a').value.set('/tmp/') await cfg.option('a').value.set('/tmp/text.txt') - await cfg.option('a').value.set('tmp') - await cfg.option('a').value.set('tmp/') - await cfg.option('a').value.set('tmp/text.txt') + await cfg.option('a').value.set('/tmp/with space.txt') + await cfg.option('a').value.set('/tmp/with$.txt') with pytest.raises(ValueError): - await cfg.option('a').value.set('/tmp/with space.txt') - with pytest.raises(ValueError): - await cfg.option('a').value.set('/tmp/with$.txt') + await cfg.option('a').value.set('not starts with /') assert not await list_sessions() diff --git a/tiramisu/option/filenameoption.py b/tiramisu/option/filenameoption.py index ad0d2cb..7abdc0e 100644 --- a/tiramisu/option/filenameoption.py +++ b/tiramisu/option/filenameoption.py @@ -21,11 +21,17 @@ import re from ..i18n import _ -from .stroption import RegexpOption +from .stroption import StrOption -class FilenameOption(RegexpOption): +class FilenameOption(StrOption): __slots__ = tuple() - _regexp = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") _type = 'filename' _display_name = _('file name') + + def validate(self, + value: str, + ) -> None: + super().validate(value) + if not value.startswith('/'): + raise ValueError(_('must starts with "/"')) From 7ebad5724e8aec32f38d405362a680c1ed91b952 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 24 Feb 2021 20:30:04 +0100 Subject: [PATCH 06/16] 2020/2021 --- tiramisu/__init__.py | 2 +- tiramisu/api.py | 2 +- tiramisu/asyncinit.py | 2 +- tiramisu/autolib.py | 2 +- tiramisu/config.py | 2 +- tiramisu/error.py | 2 +- tiramisu/function.py | 2 +- tiramisu/i18n.py | 2 +- tiramisu/log.py | 2 +- tiramisu/option/baseoption.py | 4 ++-- tiramisu/option/booloption.py | 2 +- tiramisu/option/broadcastoption.py | 2 +- tiramisu/option/choiceoption.py | 2 +- tiramisu/option/dateoption.py | 2 +- tiramisu/option/domainnameoption.py | 2 +- tiramisu/option/dynoptiondescription.py | 2 +- tiramisu/option/emailoption.py | 2 +- tiramisu/option/filenameoption.py | 2 +- tiramisu/option/floatoption.py | 2 +- tiramisu/option/intoption.py | 2 +- tiramisu/option/ipoption.py | 2 +- tiramisu/option/leadership.py | 2 +- tiramisu/option/macoption.py | 2 +- tiramisu/option/netmaskoption.py | 2 +- tiramisu/option/networkoption.py | 2 +- tiramisu/option/option.py | 2 +- tiramisu/option/optiondescription.py | 2 +- tiramisu/option/passwordoption.py | 2 +- tiramisu/option/portoption.py | 2 +- tiramisu/option/stroption.py | 2 +- tiramisu/option/symlinkoption.py | 2 +- tiramisu/option/syndynoption.py | 2 +- tiramisu/option/syndynoptiondescription.py | 2 +- tiramisu/option/urloption.py | 2 +- tiramisu/option/usernameoption.py | 2 +- tiramisu/setting.py | 2 +- tiramisu/storage/__init__.py | 2 +- tiramisu/storage/cache/dictionary.py | 2 +- tiramisu/storage/cacheobj.py | 2 +- tiramisu/storage/dictionary/__init__.py | 2 +- tiramisu/storage/dictionary/setting.py | 2 +- tiramisu/storage/dictionary/storage.py | 2 +- tiramisu/storage/dictionary/value.py | 2 +- tiramisu/storage/postgres/__init__.py | 2 +- tiramisu/storage/postgres/setting.py | 2 +- tiramisu/storage/postgres/storage.py | 2 +- tiramisu/storage/postgres/value.py | 2 +- tiramisu/storage/sqlite3/__init__.py | 2 +- tiramisu/storage/sqlite3/setting.py | 2 +- tiramisu/storage/sqlite3/sqlite3db.py | 2 +- tiramisu/storage/sqlite3/storage.py | 2 +- tiramisu/storage/sqlite3/value.py | 2 +- tiramisu/value.py | 2 +- 53 files changed, 54 insertions(+), 54 deletions(-) diff --git a/tiramisu/__init__.py b/tiramisu/__init__.py index b837d8a..7ca9e23 100644 --- a/tiramisu/__init__.py +++ b/tiramisu/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/api.py b/tiramisu/api.py index 99deb33..064f5a3 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/asyncinit.py b/tiramisu/asyncinit.py index 2d121f8..364536d 100644 --- a/tiramisu/asyncinit.py +++ b/tiramisu/asyncinit.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2019-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2019-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 71ebf43..ffa9b5e 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/config.py b/tiramisu/config.py index a9bf14d..fdf724d 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/error.py b/tiramisu/error.py index d97e0ab..0c1384d 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/function.py b/tiramisu/function.py index 0cd037f..6091530 100644 --- a/tiramisu/function.py +++ b/tiramisu/function.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2018-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/i18n.py b/tiramisu/i18n.py index d021f0e..6947b39 100644 --- a/tiramisu/i18n.py +++ b/tiramisu/i18n.py @@ -1,5 +1,5 @@ # -*- coding: UTF-8 -*- -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/log.py b/tiramisu/log.py index 6b2e1f2..5182c84 100644 --- a/tiramisu/log.py +++ b/tiramisu/log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "logger for tiramisu" -# Copyright (C) 2019-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2019-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 87b37a9..df518a3 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2014-2021 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 Lesser General Public License as published by the @@ -29,7 +29,7 @@ from ..i18n import _ from ..setting import undefined, Settings from ..value import Values from ..error import ConfigError, display_list -from ..autolib import Calculation, Params, ParamOption, ParamIndex +from ..autolib import Calculation, Params, ParamOption STATIC_TUPLE = frozenset() diff --git a/tiramisu/option/booloption.py b/tiramisu/option/booloption.py index 5a6da8f..16d16ae 100644 --- a/tiramisu/option/booloption.py +++ b/tiramisu/option/booloption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/broadcastoption.py b/tiramisu/option/broadcastoption.py index 3e1b736..363a690 100644 --- a/tiramisu/option/broadcastoption.py +++ b/tiramisu/option/broadcastoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/choiceoption.py b/tiramisu/option/choiceoption.py index 29e2f42..335f4f9 100644 --- a/tiramisu/option/choiceoption.py +++ b/tiramisu/option/choiceoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/dateoption.py b/tiramisu/option/dateoption.py index eb52777..4d78df1 100644 --- a/tiramisu/option/dateoption.py +++ b/tiramisu/option/dateoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/domainnameoption.py b/tiramisu/option/domainnameoption.py index a313b70..3b90e2b 100644 --- a/tiramisu/option/domainnameoption.py +++ b/tiramisu/option/domainnameoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/dynoptiondescription.py b/tiramisu/option/dynoptiondescription.py index 7b04750..fc462af 100644 --- a/tiramisu/option/dynoptiondescription.py +++ b/tiramisu/option/dynoptiondescription.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/emailoption.py b/tiramisu/option/emailoption.py index 2878008..398fc75 100644 --- a/tiramisu/option/emailoption.py +++ b/tiramisu/option/emailoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/filenameoption.py b/tiramisu/option/filenameoption.py index 7abdc0e..30bf751 100644 --- a/tiramisu/option/filenameoption.py +++ b/tiramisu/option/filenameoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/floatoption.py b/tiramisu/option/floatoption.py index fa881bf..61461fb 100644 --- a/tiramisu/option/floatoption.py +++ b/tiramisu/option/floatoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/intoption.py b/tiramisu/option/intoption.py index c7b1d93..89b74e6 100644 --- a/tiramisu/option/intoption.py +++ b/tiramisu/option/intoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/ipoption.py b/tiramisu/option/ipoption.py index 0ccdc20..1c4278e 100644 --- a/tiramisu/option/ipoption.py +++ b/tiramisu/option/ipoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index 81aebe4..baddcc2 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "Leadership support" -# Copyright (C) 2014-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2014-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/macoption.py b/tiramisu/option/macoption.py index 4db8e5d..d80287a 100644 --- a/tiramisu/option/macoption.py +++ b/tiramisu/option/macoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2020-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/netmaskoption.py b/tiramisu/option/netmaskoption.py index a4f5d95..f482e63 100644 --- a/tiramisu/option/netmaskoption.py +++ b/tiramisu/option/netmaskoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/networkoption.py b/tiramisu/option/networkoption.py index 9f30148..bd80b05 100644 --- a/tiramisu/option/networkoption.py +++ b/tiramisu/option/networkoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index d8151f9..4561c3d 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "option types and option description" -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 8278d37..10d1af9 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2014-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/passwordoption.py b/tiramisu/option/passwordoption.py index 008385f..514d216 100644 --- a/tiramisu/option/passwordoption.py +++ b/tiramisu/option/passwordoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/portoption.py b/tiramisu/option/portoption.py index af0ddf9..59db844 100644 --- a/tiramisu/option/portoption.py +++ b/tiramisu/option/portoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/stroption.py b/tiramisu/option/stroption.py index 58941c4..60959c8 100644 --- a/tiramisu/option/stroption.py +++ b/tiramisu/option/stroption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/symlinkoption.py b/tiramisu/option/symlinkoption.py index 22e8c5d..95ff08a 100644 --- a/tiramisu/option/symlinkoption.py +++ b/tiramisu/option/symlinkoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/syndynoption.py b/tiramisu/option/syndynoption.py index 37204a4..f893672 100644 --- a/tiramisu/option/syndynoption.py +++ b/tiramisu/option/syndynoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2018-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2018-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/syndynoptiondescription.py b/tiramisu/option/syndynoptiondescription.py index 8bf7c9f..f6dd507 100644 --- a/tiramisu/option/syndynoptiondescription.py +++ b/tiramisu/option/syndynoptiondescription.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/urloption.py b/tiramisu/option/urloption.py index 64b45b7..179ad93 100644 --- a/tiramisu/option/urloption.py +++ b/tiramisu/option/urloption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/option/usernameoption.py b/tiramisu/option/usernameoption.py index 2c25ba9..c8b36be 100644 --- a/tiramisu/option/usernameoption.py +++ b/tiramisu/option/usernameoption.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2017-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2017-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/setting.py b/tiramisu/setting.py index f6abf33..296df71 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "sets the options of the configuration objects Config object itself" -# Copyright (C) 2012-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index 91b5502..50067c1 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/cache/dictionary.py b/tiramisu/storage/cache/dictionary.py index d47e7b3..f17a8f7 100644 --- a/tiramisu/storage/cache/dictionary.py +++ b/tiramisu/storage/cache/dictionary.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2018-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2018-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/cacheobj.py b/tiramisu/storage/cacheobj.py index f8f0dcf..516b675 100644 --- a/tiramisu/storage/cacheobj.py +++ b/tiramisu/storage/cacheobj.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "cache used by storage" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/dictionary/__init__.py b/tiramisu/storage/dictionary/__init__.py index 3002a93..a45ebdc 100644 --- a/tiramisu/storage/dictionary/__init__.py +++ b/tiramisu/storage/dictionary/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 9527301..2d8dc7a 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for setting: set it in a simple dictionary" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 7e01d83..adc8128 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index c0f5b0b..027c78f 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for value: set it in a simple dictionary" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/postgres/__init__.py b/tiramisu/storage/postgres/__init__.py index bb75c17..0417e9d 100644 --- a/tiramisu/storage/postgres/__init__.py +++ b/tiramisu/storage/postgres/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2020-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/postgres/setting.py b/tiramisu/storage/postgres/setting.py index bb339cf..8ff6346 100644 --- a/tiramisu/storage/postgres/setting.py +++ b/tiramisu/storage/postgres/setting.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for setting: set it in a simple dictionary" -# Copyright (C) 2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2020-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/postgres/storage.py b/tiramisu/storage/postgres/storage.py index b4caeb2..15f6e45 100644 --- a/tiramisu/storage/postgres/storage.py +++ b/tiramisu/storage/postgres/storage.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2020-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/postgres/value.py b/tiramisu/storage/postgres/value.py index a474b6b..3d0b6a9 100644 --- a/tiramisu/storage/postgres/value.py +++ b/tiramisu/storage/postgres/value.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2020-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/sqlite3/__init__.py b/tiramisu/storage/sqlite3/__init__.py index 2b08283..1d98bb7 100644 --- a/tiramisu/storage/sqlite3/__init__.py +++ b/tiramisu/storage/sqlite3/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index a4cbfc0..91bc230 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for setting: set it in a simple dictionary" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py index e9073c4..fbc876f 100644 --- a/tiramisu/storage/sqlite3/sqlite3db.py +++ b/tiramisu/storage/sqlite3/sqlite3db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "sqlite3" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index 82fc9df..ba78302 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- " with sqlite3 engine" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/storage/sqlite3/value.py b/tiramisu/storage/sqlite3/value.py index ae98d2c..e554673 100644 --- a/tiramisu/storage/sqlite3/value.py +++ b/tiramisu/storage/sqlite3/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for value: set it in a simple dictionary" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the diff --git a/tiramisu/value.py b/tiramisu/value.py index f492d0f..3d299d6 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "takes care of the option's values and multi values" -# Copyright (C) 2013-2020 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2021 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 Lesser General Public License as published by the From acc86bc49f7b70f35e14df0dec6bfada2b6126a3 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 6 Mar 2021 19:23:35 +0100 Subject: [PATCH 07/16] Add ParamSelfInformation --- tests/test_option_callback.py | 24 ++++++++++++++++- tiramisu/__init__.py | 3 ++- tiramisu/api.py | 27 ++++++++++--------- tiramisu/autolib.py | 15 ++++++++++- tiramisu/config.py | 12 ++++++--- tiramisu/option/option.py | 26 ++++++++++++------ tiramisu/storage/dictionary/value.py | 8 ++---- tiramisu/storage/postgres/value.py | 10 +++---- tiramisu/value.py | 40 +++++++++++++++++++++------- 9 files changed, 114 insertions(+), 51 deletions(-) diff --git a/tests/test_option_callback.py b/tests/test_option_callback.py index 5edc363..97f468b 100644 --- a/tests/test_option_callback.py +++ b/tests/test_option_callback.py @@ -10,7 +10,7 @@ from tiramisu.setting import groups, owners from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \ StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \ undefined, Calculation, Params, ParamOption, ParamValue, ParamIndex, calc_value, \ - valid_ip_netmask, ParamSelfOption, ParamInformation + valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError from tiramisu.i18n import _ from tiramisu.storage import list_sessions @@ -378,6 +378,28 @@ async def test_callback_information(config_type): await cfg.information.set('information', 'new_value') assert await cfg.option('val1').value.get() == 'new_value' assert await cfg.option('val2').value.get() == 'new_value' + await cfg.information.set('information', 'new_value2') + assert await cfg.option('val1').value.get() == 'new_value2' + assert await cfg.option('val2').value.get() == 'new_value2' + assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_callback_information2(config_type): + val1 = StrOption('val1', "", Calculation(return_value, Params(ParamSelfInformation('information', 'no_value')))) + val2 = StrOption('val2', "", Calculation(return_value, Params(ParamSelfInformation('information')))) + val2.impl_set_information('information', 'new_value') + val3 = StrOption('val3', "", Calculation(return_value, Params(ParamSelfInformation('information')))) + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3]) + async with await Config(maconfig) as cfg: + await cfg.property.read_write() + cfg = await get_config(cfg, config_type) + assert await cfg.option('val1').value.get() == 'no_value' + assert await cfg.option('val2').value.get() == 'new_value' + with pytest.raises(ConfigError): + await cfg.option('val3').value.get() + await cfg.option('val2').information.set('information', 'new_value2') + assert await cfg.option('val2').value.get() == 'new_value2' assert not await list_sessions() diff --git a/tiramisu/__init__.py b/tiramisu/__init__.py index 7ca9e23..038fc95 100644 --- a/tiramisu/__init__.py +++ b/tiramisu/__init__.py @@ -18,7 +18,7 @@ from .function import calc_value, calc_value_property_help, valid_ip_netmask, \ valid_network_netmask, valid_in_network, valid_broadcast, \ valid_not_equal from .autolib import Calculation, Params, ParamOption, ParamDynOption, ParamSelfOption, \ - ParamValue, ParamIndex, ParamSuffix, ParamInformation + ParamValue, ParamIndex, ParamSuffix, ParamInformation, ParamSelfInformation from .option import * from .error import APIError from .api import Config, MetaConfig, GroupConfig, MixConfig @@ -37,6 +37,7 @@ allfuncs = ['Calculation', 'ParamIndex', 'ParamSuffix', 'ParamInformation', + 'ParamSelfInformation', 'MetaConfig', 'MixConfig', 'GroupConfig', diff --git a/tiramisu/api.py b/tiramisu/api.py index 064f5a3..c1dd4e8 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -464,24 +464,23 @@ class TiramisuOptionInformation(CommonTiramisuOption): @option_and_connection async def get(self, key, default=undefined): """Get information""" - path = self._option_bag.path values = self._option_bag.config_bag.context.cfgimpl_get_values() - try: - return await values.get_information(self._option_bag.config_bag.connection, - key, - path=path) - except ValueError: - return self._option_bag.option.impl_get_information(key, default) + return await values.get_information(self._option_bag.config_bag, + self._option_bag, + key, + default, + ) @option_and_connection async def set(self, key, value): """Set information""" path = self._option_bag.path values = self._option_bag.config_bag.context.cfgimpl_get_values() - await values.set_information(self._option_bag.config_bag.connection, + await values.set_information(self._option_bag.config_bag, + self._option_bag, key, value, - path=path) + ) @option_and_connection async def reset(self, @@ -890,10 +889,12 @@ class TiramisuContextInformation(TiramisuConfig): default=undefined, ): """Get an information""" - return await self._config_bag.context.impl_get_information(self._config_bag.connection, - name, - default, - ) + values = self._config_bag.context.cfgimpl_get_values() + return await values.get_information(self._config_bag, + None, + name, + default, + ) @connection async def set(self, diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index ffa9b5e..dad5934 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -128,6 +128,10 @@ class ParamInformation(Param): self.default_value = default_value +class ParamSelfInformation(ParamInformation): + __slots__ = tuple() + + class ParamIndex(Param): __slots__ = tuple() @@ -297,8 +301,17 @@ async def manager_callback(callbk: Param, return callbk.value if isinstance(callbk, ParamInformation): + if isinstance(callbk, ParamSelfInformation): + option_bag = OptionBag() + option_bag.set_option(option, + index, + config_bag, + ) + else: + option_bag = None try: - return await config_bag.context.impl_get_information(config_bag.connection, + return await config_bag.context.impl_get_information(config_bag, + option_bag, callbk.information_name, callbk.default_value, ) diff --git a/tiramisu/config.py b/tiramisu/config.py index fdf724d..cd793d9 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -572,7 +572,8 @@ class _CommonConfig(SubConfig): :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ - await self._impl_values.set_information(config_bag.connection, + await self._impl_values.set_information(config_bag, + None, key, value) for option in config_bag.context.cfgimpl_get_description()._cache_dependencies_information.get(key, []): @@ -583,14 +584,17 @@ class _CommonConfig(SubConfig): await config_bag.context.cfgimpl_reset_cache(option_bag) async def impl_get_information(self, - connection, + config_bag, + option_bag, key, - default=undefined): + default, + ): """retrieves one information's item :param key: the item string (ex: "help") """ - return await self._impl_values.get_information(connection, + return await self._impl_values.get_information(config_bag, + option_bag, key, default) diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index 4561c3d..a576e57 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -27,7 +27,7 @@ from itertools import chain from .baseoption import BaseOption, submulti, STATIC_TUPLE from ..i18n import _ from ..setting import undefined, OptionBag, Undefined -from ..autolib import Calculation, Params, ParamOption, ParamInformation +from ..autolib import Calculation, Params, ParamOption, ParamInformation, ParamSelfInformation from ..error import (ConfigError, ValueWarning, ValueErrorWarning, PropertiesOptionError, ValueOptionError, display_list) from .syndynoption import SynDynOption @@ -67,7 +67,7 @@ class Option(BaseOption): warnings_only: bool=False, extra: Optional[Dict]=None): _setattr = object.__setattr__ - _dependencies_information = [] + _dependencies_information = [[], []] if not multi and default_multi is not None: raise ValueError(_("default_multi is set whereas multi is False" " in option: {0}").format(name)) @@ -107,8 +107,10 @@ class Option(BaseOption): if isinstance(param, ParamOption): param.option._add_dependency(self) self._has_dependency = True + elif isinstance(param, ParamSelfInformation): + _dependencies_information[1].append(param.information_name) elif isinstance(param, ParamInformation): - _dependencies_information.append(param.information_name) + _dependencies_information[0].append(param.information_name) self._validators = tuple(validators) if extra is not None and extra != {}: @@ -165,7 +167,7 @@ class Option(BaseOption): if is_multi and isinstance(default, list): default = tuple(default) _setattr(self, '_default', default) - if _dependencies_information: + if _dependencies_information[0] or _dependencies_information[1]: self._dependencies_information = _dependencies_information def value_dependencies(self, @@ -188,8 +190,10 @@ class Option(BaseOption): for param in chain(value.params.args, value.params.kwargs.values()): if isinstance(param, ParamOption): param.option._add_dependency(self) + elif isinstance(param, ParamSelfInformation): + _dependencies_information[1].append(param.information_name) elif isinstance(param, ParamInformation): - _dependencies_information.append(param.information_name) + _dependencies_information[0].append(param.information_name) #__________________________________________________________________________ # option's information @@ -203,8 +207,14 @@ class Option(BaseOption): def impl_is_dynsymlinkoption(self) -> bool: return False - def get_dependencies_information(self) -> List[str]: - return getattr(self, '_dependencies_information', []) + def get_dependencies_information(self, + itself=False, + ) -> List[str]: + if itself: + idx = 1 + else: + idx = 0 + return getattr(self, '_dependencies_information', [[], []])[idx] def get_type(self) -> str: # _display_name for compatibility with older version than 3.0rc3 @@ -449,7 +459,7 @@ class Option(BaseOption): self._display_name, option_bag.ori_option, '{0}'.format(err), - err_index) + err_index) from err warnings.warn_explicit(ValueErrorWarning(val, self._display_name, option_bag.ori_option, diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index 027c78f..b3a7f80 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -257,16 +257,12 @@ class Values: connection, path, key, - default): + ): """retrieves one information's item :param key: the item string (ex: "help") """ - value = self._storage.get_informations().get(path, {}).get(key, default) - if value is undefined: - raise ValueError(_("information's item" - " not found: {0}").format(key)) - return value + return self._storage.get_informations().get(path, {})[key] async def del_information(self, connection, diff --git a/tiramisu/storage/postgres/value.py b/tiramisu/storage/postgres/value.py index 3d0b6a9..2f3f5c5 100644 --- a/tiramisu/storage/postgres/value.py +++ b/tiramisu/storage/postgres/value.py @@ -186,7 +186,7 @@ class Values: connection, path, key, - default): + ): """retrieves one information's item :param key: the item string (ex: "help") @@ -197,12 +197,8 @@ class Values: "session_id = $2 AND path = $3", key, self._storage.database_id, path) if value is None: - if default is undefined: - raise ValueError(_("information's item" - " not found: {0}").format(key)) - return default - else: - return loads(value) + raise KeyError() + return loads(value) async def del_information(self, connection, diff --git a/tiramisu/value.py b/tiramisu/value.py index 3d299d6..f5afe1a 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -583,33 +583,53 @@ class Values: # information async def set_information(self, - connection, + config_bag, + option_bag, key, value, - path=None): + ): """updates the information's attribute :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ - await self._p_.set_information(connection, + if option_bag is None: + path = None + else: + path = option_bag.path + await self._p_.set_information(config_bag.connection, path, key, value) + if path is not None: + for option in option_bag.option.get_dependencies_information(itself=True): + await config_bag.context.cfgimpl_reset_cache(option_bag) async def get_information(self, - connection, + config_bag, + option_bag, key, - default=undefined, - path=None): + default, + ): """retrieves one information's item :param key: the item string (ex: "help") """ - return await self._p_.get_information(connection, - path, - key, - default) + if option_bag is None: + path = None + else: + path = option_bag.path + try: + return await self._p_.get_information(config_bag.connection, + path, + key, + ) + except KeyError as err: + if option_bag: + return option_bag.option.impl_get_information(key, default) + if default is not undefined: + return default + raise ValueError(_("information's item not found: {0}").format(key)) async def del_information(self, connection, From 0167f4e2d0bfb29aa853537fa21d27d3713ac34e Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Mon, 8 Mar 2021 06:39:40 +0100 Subject: [PATCH 08/16] manage callback with unrestraint --- tests/test_option_callback.py | 15 +++++++++++++++ tiramisu/autolib.py | 33 ++++++++++++++++++++++++--------- tiramisu/setting.py | 8 ++++++-- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/tests/test_option_callback.py b/tests/test_option_callback.py index 97f468b..b31a50c 100644 --- a/tests/test_option_callback.py +++ b/tests/test_option_callback.py @@ -1298,6 +1298,21 @@ async def test_callback_calculating_invalid(): assert not await list_sessions() +@pytest.mark.asyncio +async def test_callback_unrestraint(): + opt1 = IntOption('opt1', '', 1) + opt2 = IntOption('opt2', '', Calculation(return_value, Params(ParamOption(opt1)))) + od1 = OptionDescription('od1', '', [opt1], properties=('disabled',)) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + async with await Config(maconfig) as cfg: + await cfg.property.read_write() + with pytest.raises(ConfigError): + await cfg.option('od2.opt2').value.get() + assert await cfg.unrestraint.option('od2.opt2').value.get() == 1 + assert not await list_sessions() + + @pytest.mark.asyncio async def test_callback_calculating_disabled(): opt1 = BoolOption('opt1', '', properties=('disabled',)) diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index dad5934..18f589a 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -170,8 +170,9 @@ class Calculation: option_bag: OptionBag, leadership_must_have_index: bool=False, orig_value: Any=undefined, - allow_value_error=False, - force_value_warning=False, + allow_value_error: bool=False, + force_value_warning: bool=False, + for_settings: bool=False, ) -> Any: return await carry_out_calculation(option_bag.option, callback=self.function, @@ -182,20 +183,27 @@ class Calculation: orig_value=orig_value, allow_value_error=allow_value_error, force_value_warning=force_value_warning, + for_settings=for_settings, ) async def help(self, option_bag: OptionBag, - leadership_must_have_index: bool=False) -> str: + leadership_must_have_index: bool=False, + for_settings: bool=False, + ) -> str: if not self.help_function: return await self.execute(option_bag, - leadership_must_have_index=leadership_must_have_index) + leadership_must_have_index=leadership_must_have_index, + for_settings=for_settings, + ) return await carry_out_calculation(option_bag.option, callback=self.help_function, callback_params=self.params, index=option_bag.index, config_bag=option_bag.config_bag, - leadership_must_have_index=leadership_must_have_index) + leadership_must_have_index=leadership_must_have_index, + for_settings=for_settings, + ) def has_index(self, current_option): if hasattr(self, '_has_index'): @@ -218,7 +226,9 @@ async def manager_callback(callbk: Param, index: Optional[int], orig_value, config_bag: ConfigBag, - leadership_must_have_index: bool) -> Any: + leadership_must_have_index: bool, + for_settings: bool, + ) -> Any: """replace Param by true value""" def calc_index(callbk, index, same_leadership): if index is not None: @@ -280,9 +290,11 @@ async def manager_callback(callbk: Param, self_calc): # don't validate if option is option that we tried to validate config_bag = config_bag.copy() - config_bag.properties = config_bag.true_properties - {'warnings'} + if for_settings: + config_bag.properties = config_bag.true_properties - {'warnings'} config_bag.set_permissive() - #config_bag.properties -= {'warnings'} + if not for_settings: + config_bag.properties -= {'warnings'} option_bag = OptionBag() option_bag.set_option(opt, index_, @@ -401,6 +413,7 @@ async def carry_out_calculation(option, leadership_must_have_index: bool=False, allow_value_error: bool=False, force_value_warning: bool=False, + for_settings: bool=False, ): """a function that carries out a calculation for an option's value @@ -430,7 +443,9 @@ async def carry_out_calculation(option, index, orig_value, config_bag, - leadership_must_have_index) + leadership_must_have_index, + for_settings, + ) if value is undefined: return undefined if key is None: diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 296df71..36a4abe 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -459,10 +459,14 @@ class Settings(object): elif apply_requires: if not help_property: new_prop = await prop.execute(option_bag, - leadership_must_have_index=True) + leadership_must_have_index=True, + for_settings=True, + ) else: new_prop = await prop.help(option_bag, - leadership_must_have_index=True) + leadership_must_have_index=True, + for_settings=True, + ) if isinstance(new_prop, str): new_prop = (new_prop, new_prop) if new_prop is None: From da946baba63204d7aa5baa41c7c9b2691cde702c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 18 Mar 2021 08:52:22 +0100 Subject: [PATCH 09/16] Calculation to non-leader option with leader parameter is now possible --- tiramisu/value.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiramisu/value.py b/tiramisu/value.py index f5afe1a..a14ae83 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -76,6 +76,7 @@ class Values: setting_properties, option_bag.properties, 'value') + # FIXME hu? validated or is_cached? if not validated: # no cached value so get value value = await self.getvalue(option_bag) @@ -169,8 +170,7 @@ class Values: value, reset_cache=True): if isinstance(value, Calculation): - value = await value.execute(option_bag, - leadership_must_have_index=True) + value = await value.execute(option_bag) elif isinstance(value, (list, tuple)): value = await self._do_value_list(value, option_bag) if reset_cache: From 668ed3ad3742deed93596a748b4012b0341d5b2f Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 18 Mar 2021 08:54:59 +0100 Subject: [PATCH 10/16] better error when value not in a choice --- tiramisu/error.py | 2 ++ tiramisu/option/choiceoption.py | 4 ++-- tiramisu/option/syndynoptiondescription.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tiramisu/error.py b/tiramisu/error.py index 0c1384d..c79a26a 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -20,6 +20,8 @@ from .i18n import _ def display_list(lst, separator='and', add_quote=False): + if not lst: + return '""' if separator == 'and': separator = _('and') elif separator == 'or': diff --git a/tiramisu/option/choiceoption.py b/tiramisu/option/choiceoption.py index 335f4f9..b971388 100644 --- a/tiramisu/option/choiceoption.py +++ b/tiramisu/option/choiceoption.py @@ -60,8 +60,8 @@ class ChoiceOption(Option): if isinstance(self._choice_values, Calculation): values = await self._choice_values.execute(option_bag) if values is not undefined and not isinstance(values, list): - raise ConfigError(_('calculated values for {0} is not a list' - '').format(self.impl_getname())) + raise ConfigError(_('the calculated values "{0}" for "{1}" is not a list' + '').format(values, self.impl_getname())) else: values = self._choice_values return values diff --git a/tiramisu/option/syndynoptiondescription.py b/tiramisu/option/syndynoptiondescription.py index f6dd507..6df1cf5 100644 --- a/tiramisu/option/syndynoptiondescription.py +++ b/tiramisu/option/syndynoptiondescription.py @@ -84,7 +84,8 @@ class SynDynOptionDescription: async def get_children(self, config_bag: ConfigBag, - dyn: bool=True): + dyn: bool=True, + ): subpath = self.impl_getpath() children = [] for child in await self._opt.get_children(config_bag): From 29282d8ea36b4b8962a5260c42fd924992a60e5e Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 18 Mar 2021 08:57:22 +0100 Subject: [PATCH 11/16] calculation with submulti --- tiramisu/option/leadership.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index baddcc2..c36824f 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -69,11 +69,21 @@ class Leadership(OptionDescription): child.impl_get_display_name())) if idx != 0: default = child.impl_getdefault() - if default != [] and not isinstance(default, Calculation): - raise ValueError(_('not allowed default value for follower option "{0}" ' - 'in leadership "{1}"' - '').format(child.impl_get_display_name(), - self.impl_get_display_name())) + if default != []: + if child.impl_is_submulti() and isinstance(default, tuple): + for val in default: + if not isinstance(val, Calculation): + calculation = False + else: + # empty default is valid + calculation = True + else: + calculation = isinstance(default, Calculation) + if not calculation: + raise ValueError(_('not allowed default value for follower option "{0}" ' + 'in leadership "{1}"' + '').format(child.impl_get_display_name(), + self.impl_get_display_name())) if idx != 0: # remove empty property for follower child._properties = frozenset(child._properties - {'empty', 'unique'}) From 8972e796dbe64a9c9899bb6bc492e9512b98320c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 18 Mar 2021 09:00:04 +0100 Subject: [PATCH 12/16] dict with leader_to_list parameter --- tests/test_leadership.py | 23 ++++++++++++- tiramisu/config.py | 49 ++++++++++++++++++++-------- tiramisu/option/optiondescription.py | 3 +- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/tests/test_leadership.py b/tests/test_leadership.py index 3f8c34a..f2a8f9b 100644 --- a/tests/test_leadership.py +++ b/tests/test_leadership.py @@ -388,7 +388,28 @@ async def test_groups_with_leader_hidden_in_config(): await cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() with pytest.raises(PropertiesOptionError): await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() - await cfg.value.dict(leader_to_list=True) == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': '192.168.1.1'}]} + assert await cfg.value.dict() == {} + assert await cfg.value.dict(leader_to_list=True) == {} + assert not await list_sessions() + + +async def test_groups_with_leader_hidden_in_config2(): + 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, properties=('hidden',)) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + od = OptionDescription('root', '', [interface1]) + async with await Config(od) as cfg: + await cfg.property.read_write() + await cfg.permissive.add('hidden') + assert await cfg.forcepermissive.option('ip_admin_eth0.ip_admin_eth0').value.get() == [] + await cfg.forcepermissive.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.1']) + assert await cfg.forcepermissive.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + with pytest.raises(PropertiesOptionError): + await cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() + with pytest.raises(PropertiesOptionError): + await cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() + assert await cfg.value.dict() == {'ip_admin_eth0.ip_admin_eth0': ['192.168.1.1']} + assert await cfg.value.dict(leader_to_list=True) == {'ip_admin_eth0.ip_admin_eth0': [{'ip_admin_eth0.ip_admin_eth0': '192.168.1.1'}]} assert not await list_sessions() diff --git a/tiramisu/config.py b/tiramisu/config.py index cd793d9..c9a34a9 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -111,19 +111,12 @@ class SubConfig: for woption in option_bag.option._get_dependencies(self.cfgimpl_get_description()): option = woption() if option.impl_is_dynoptiondescription(): - subpath = option.impl_getpath().rsplit('.', 1)[0] + path = option.impl_getpath() + if '.' in path: + subpath = option.impl_getpath().rsplit('.', 1)[0] + else: + subpath = '' for suffix in await option.get_suffixes(option_bag.config_bag): - doption = option.to_dynoption(subpath, - suffix, - option) - doption_bag = OptionBag() - doption_bag.set_option(doption, - option_bag.index, - option_bag.config_bag) - doption_bag.properties = await self.cfgimpl_get_settings().getproperties(doption_bag) - await self.reset_one_option_cache(desc, - resetted_opts, - doption_bag) async for coption in self.cfgimpl_get_description().get_children_recursively(None, None, option_bag.config_bag): @@ -131,11 +124,20 @@ class SubConfig: coption_bag.set_option(coption, option_bag.index, option_bag.config_bag) - coption_bag.properties = await self.cfgimpl_get_settings().getproperties(coption_bag) await self.reset_one_option_cache(option, resetted_opts, coption_bag, ) + doption = option.to_dynoption(subpath, + suffix, + option) + doption_bag = OptionBag() + doption_bag.set_option(doption, + option_bag.index, + option_bag.config_bag) + await self.reset_one_option_cache(desc, + resetted_opts, + doption_bag) elif option.issubdyn(): # it's an option in dynoptiondescription, remove cache for all generated option dynopt = option.getsubdyn() @@ -151,7 +153,7 @@ class SubConfig: doption_bag.set_option(doption, option_bag.index, option_bag.config_bag) - doption_bag.properties = await self.cfgimpl_get_settings().getproperties(doption_bag) + #doption_bag.properties = await self.cfgimpl_get_settings().getproperties(doption_bag) await self.reset_one_option_cache(desc, resetted_opts, doption_bag) @@ -180,9 +182,16 @@ class SubConfig: context = self.cfgimpl_get_context() desc = context.cfgimpl_get_description() if option_bag is not None: + if 'cache' in option_bag.config_bag.properties: + has_cache = True + option_bag.config_bag.properties = option_bag.config_bag.properties - {'cache'} + else: + has_cache = False await self.reset_one_option_cache(desc, resetted_opts, option_bag) + if has_cache: + option_bag.config_bag.properties = option_bag.config_bag.properties | {'cache'} else: context._impl_values_cache.reset_all_cache() context._impl_properties_cache.reset_all_cache() @@ -435,6 +444,18 @@ class SubConfig: for opt in await self.cfgimpl_get_description().get_children(config_bag): if leader_to_list and opt.impl_is_optiondescription() and opt.impl_is_leadership(): # leader + try: + loption_bag = OptionBag() + loption_bag.set_option(opt, + None, + config_bag) + loption_bag.properties = await self.cfgimpl_get_settings().getproperties(loption_bag) + await self.cfgimpl_get_settings().validate_properties(loption_bag, + need_help=False) + except PropertiesOptionError as err: + if err.proptype in (['mandatory'], ['empty']): + raise err + continue children = await opt.get_children(config_bag) leader = children[0] loption_bag = OptionBag() diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 10d1af9..c66fb57 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -187,7 +187,8 @@ class OptionDescriptionWalk(CacheOptionDescription): async def get_children(self, config_bag: Union[ConfigBag, Undefined], - dyn: bool=True) -> Union[BaseOption, SynDynOptionDescription]: + dyn: bool=True, + ) -> Union[BaseOption, SynDynOptionDescription]: if not dyn or config_bag is undefined or \ config_bag.context.cfgimpl_get_description() == self: subpath = '' From 12174045a2f20905cbfdf25d075b7bf4c9489a91 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 18 Mar 2021 09:00:28 +0100 Subject: [PATCH 13/16] add test to dyn optiondescription --- tests/test_dyn_optiondescription.py | 67 +++++++++++++++++++++++++ tiramisu/option/dynoptiondescription.py | 2 + 2 files changed, 69 insertions(+) diff --git a/tests/test_dyn_optiondescription.py b/tests/test_dyn_optiondescription.py index 481f7dc..d6f1a4f 100644 --- a/tests/test_dyn_optiondescription.py +++ b/tests/test_dyn_optiondescription.py @@ -642,6 +642,73 @@ async def test_dyndescription_root(): assert not await list_sessions() +@pytest.mark.asyncio +async def test_dyndescription_disable_suffix_root(): + boolean = BoolOption('boolean', '', True) + disabled_property = Calculation(calc_value, + Params(ParamValue('disabled'), + kwargs={'condition': ParamOption(boolean), + 'expected': ParamValue(False), + 'default': ParamValue(None)})) + val = StrOption('val', '', ['ext1', 'ext2'], properties=(disabled_property,), multi=True) + st1 = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st1], suffixes=Calculation(calc_value, Params(ParamOption(val, notraisepropertyerror=True))), properties=(disabled_property,)) + od = OptionDescription('root', 'root', [boolean, val, dod]) + config = await Config(od, session_id='test') + await config.property.read_write() + assert await config.value.dict() == {'boolean': True, 'val': ['ext1', 'ext2'], 'dodext1.stext1': None, 'dodext2.stext2': None} + # + await config.option('boolean').value.set(False) + assert await config.value.dict() == {'boolean': False} + await delete_session('test') + assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_dyndescription_disable_suffix_root_2(): + boolean = BoolOption('boolean', '', False) + disabled_property = Calculation(calc_value, + Params(ParamValue('disabled'), + kwargs={'condition': ParamOption(boolean), + 'expected': ParamValue(False), + 'default': ParamValue(None)})) + val = StrOption('val', '', ['ext1', 'ext2'], properties=(disabled_property,), multi=True) + st1 = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st1], suffixes=Calculation(calc_value, Params(ParamOption(val, notraisepropertyerror=True))), properties=(disabled_property,)) + od = OptionDescription('root', 'root', [boolean, val, dod]) + config = await Config(od, session_id='test') + await config.property.read_write() + assert await config.value.dict() == {'boolean': False} + # + await config.option('boolean').value.set(True) + assert await config.value.dict() == {'boolean': True, 'val': ['ext1', 'ext2'], 'dodext1.stext1': None, 'dodext2.stext2': None} + await delete_session('test') + assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_dyndescription_disable_suffix(): + boolean = BoolOption('boolean', '', True) + disabled_property = Calculation(calc_value, + Params(ParamValue('disabled'), + kwargs={'condition': ParamOption(boolean), + 'expected': ParamValue(False), + 'default': ParamValue(None)})) + val = StrOption('val', '', ['ext1', 'ext2'], properties=(disabled_property,), multi=True) + st1 = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st1], suffixes=Calculation(calc_value, Params(ParamOption(val, notraisepropertyerror=True))), properties=(disabled_property,)) + od = OptionDescription('root', 'root', [boolean, val, dod]) + od2 = OptionDescription('root', 'root', [od]) + config = await Config(od2, session_id='test') + await config.property.read_write() + assert await config.value.dict() == {'root.boolean': True, 'root.val': ['ext1', 'ext2'], 'root.dodext1.stext1': None, 'root.dodext2.stext2': None} + # + await config.option('root.boolean').value.set(False) + assert await config.value.dict() == {'root.boolean': False} + await delete_session('test') + assert not await list_sessions() + + @pytest.mark.asyncio async def test_requires_dyndescription(): boolean = BoolOption('boolean', '', True) diff --git a/tiramisu/option/dynoptiondescription.py b/tiramisu/option/dynoptiondescription.py index fc462af..f341f5c 100644 --- a/tiramisu/option/dynoptiondescription.py +++ b/tiramisu/option/dynoptiondescription.py @@ -80,6 +80,8 @@ class DynOptionDescription(OptionDescription): None, config_bag) values = await self._suffixes.execute(option_bag) + if values is None: + values = [] if __debug__: if not isinstance(values, list): raise ValueError(_('DynOptionDescription suffixes for option "{}", is not a list ({})' From 3b1dccd844b648f03841df71bcf67c323ed88335 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 21 Mar 2021 14:51:12 +0100 Subject: [PATCH 14/16] allow caching with the demoting_error_warning property --- tests/test_cache.py | 29 ++++++++++++++++++++++++ tiramisu/option/option.py | 9 +++++--- tiramisu/value.py | 47 +++++++++++++++++++++------------------ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/tests/test_cache.py b/tests/test_cache.py index 94b0a74..4f41d2d 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -559,6 +559,8 @@ async def test_cache_global_properties(): @pytest.mark.asyncio async def test_callback_value_incr(): + global incr + incr = -1 val1 = IntOption('val1', "", Calculation(return_incr), properties=('expire',)) val2 = IntOption('val2', "", Calculation(calc_value, Params(ParamOption(val1)))) od1 = OptionDescription('rootconfig', '', [val1, val2]) @@ -579,3 +581,30 @@ async def test_callback_value_incr(): assert await cfg.option('val1').value.get() == 2 assert await cfg.option('val2').value.get() == 2 assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_callback_value_incr_demoting(): + global incr + incr = -1 + val1 = IntOption('val1', "", Calculation(return_incr), properties=('expire',)) + val2 = IntOption('val2', "", Calculation(calc_value, Params(ParamOption(val1)))) + od1 = OptionDescription('rootconfig', '', [val1, val2]) + async with await Config(od1) as cfg: + await cfg.property.add('demoting_error_warning') + assert await cfg.cache.get_expiration_time() == 5 + await cfg.cache.set_expiration_time(1) + assert await cfg.cache.get_expiration_time() == 1 + await cfg.property.read_write() + assert await cfg.option('val1').value.get() == 1 + sleep(1) + assert await cfg.option('val2').value.get() == 1 + sleep(1) + assert await cfg.option('val1').value.get() == 1 + assert await cfg.option('val2').value.get() == 1 + sleep(2) + assert await cfg.option('val1').value.get() == 2 + assert await cfg.option('val2').value.get() == 2 + assert await cfg.option('val1').value.get() == 2 + assert await cfg.option('val2').value.get() == 2 + assert not await list_sessions() diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index a576e57..b9ea370 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -333,7 +333,8 @@ class Option(BaseOption): value: Any, option_bag: OptionBag, check_error: bool=True) -> None: - """ + """Return True if value is really valid + If not validate or invalid return it returns False """ config_bag = option_bag.config_bag force_index = option_bag.index @@ -341,7 +342,7 @@ class Option(BaseOption): if check_error and config_bag is not undefined and \ not 'validator' in config_bag.properties: - return + return False def _is_not_unique(value, option_bag): @@ -390,7 +391,7 @@ class Option(BaseOption): raise ValueError(_('which must not be a list').format(_value, self.impl_get_display_name())) if isinstance(_value, Calculation) and config_bag is undefined: - return + return False if _value is not None: if check_error: # option validation @@ -467,6 +468,8 @@ class Option(BaseOption): err_index), ValueErrorWarning, self.__class__.__name__, 0) + return False + return True def _validate_calculator(self, callback: Callable, diff --git a/tiramisu/value.py b/tiramisu/value.py index a14ae83..8fe229c 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -57,11 +57,12 @@ class Values: None, new=True) + #______________________________________________________________________ # get value - async def get_cached_value(self, - option_bag): + option_bag: OptionBag, + ) -> Any: """get value directly in cache if set otherwise calculated value and set it in cache @@ -75,32 +76,34 @@ class Values: option_bag.index, setting_properties, option_bag.properties, - 'value') - # FIXME hu? validated or is_cached? - if not validated: - # no cached value so get value + 'value', + ) + # no cached value so get value + if not is_cached: value = await self.getvalue(option_bag) - # validate value - await option_bag.option.impl_validate(value, - option_bag, - check_error=True) - # store value in cache - validator = 'validator' in setting_properties and 'demoting_error_warning' not in setting_properties - if not is_cached or validator: - cache.setcache(option_bag.path, - option_bag.index, - value, - option_bag.properties, - setting_properties, - validator) + # validates and warns value + if not validated: + validate = await option_bag.option.impl_validate(value, + option_bag, + check_error=True, + ) if 'warnings' in setting_properties: await option_bag.option.impl_validate(value, option_bag, - check_error=False) + check_error=False, + ) + # set value to cache + if not is_cached: + cache.setcache(option_bag.path, + option_bag.index, + value, + option_bag.properties, + setting_properties, + validate, + ) if isinstance(value, list): # return a copy, so value cannot be modified - from copy import copy - value = copy(value) + value = value.copy() # and return it return value From f7721a9775b724339de9677717184588af6a43c9 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 3 Apr 2021 20:24:27 +0200 Subject: [PATCH 15/16] manage force_store_value with dynoption --- tests/test_dyn_optiondescription.py | 29 ++++++++++++-- tiramisu/config.py | 3 +- tiramisu/option/baseoption.py | 16 +++++++- tiramisu/option/dynoptiondescription.py | 4 +- tiramisu/option/optiondescription.py | 48 +++++++++++++++-------- tiramisu/value.py | 51 +++++++++++++++++++++++-- 6 files changed, 125 insertions(+), 26 deletions(-) diff --git a/tests/test_dyn_optiondescription.py b/tests/test_dyn_optiondescription.py index d6f1a4f..62373ca 100644 --- a/tests/test_dyn_optiondescription.py +++ b/tests/test_dyn_optiondescription.py @@ -260,9 +260,32 @@ async def test_prop_dyndescription_force_store_value(): dod = DynOptionDescription('dod', '', [st], suffixes=Calculation(return_list)) od = OptionDescription('od', '', [dod]) od2 = OptionDescription('od', '', [od]) - with pytest.raises(ConfigError): - await Config(od2, session_id='error') - await delete_session('error') + async with await Config(od2) as cfg: + await cfg.property.read_write() + assert await cfg.value.dict() == {'od.dodval1.stval1': None, 'od.dodval2.stval2': None} + assert not await list_sessions() + + +@pytest.mark.asyncio +async def test_prop_dyndescription_force_store_value_calculation_prefix(): + print() + lst = StrOption('lst', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', Calculation(return_list, Params(ParamSuffix())) , properties=('force_store_value',)) + dod = DynOptionDescription('dod', '', [st], suffixes=Calculation(return_list, Params(ParamOption(lst)))) + od = OptionDescription('od', '', [dod, lst]) + od2 = OptionDescription('od', '', [od]) + async with await Config(od2) as cfg: + await cfg.property.read_write() + assert await cfg.option('od.dodval1.stval1').owner.isdefault() == False + assert await cfg.option('od.dodval2.stval2').owner.isdefault() == False + assert await cfg.value.dict() == {'od.lst': ['val1', 'val2'], 'od.dodval1.stval1': 'val1', 'od.dodval2.stval2': 'val2'} + # + await cfg.option('od.lst').value.set(['val1', 'val2', 'val3']) + assert await cfg.option('od.dodval3.stval3').owner.isdefault() == False + assert await cfg.option('od.dodval1.stval1').owner.isdefault() == False + assert await cfg.option('od.dodval2.stval2').owner.isdefault() == False + assert await cfg.value.dict() == {'od.lst': ['val1', 'val2', 'val3'], 'od.dodval1.stval1': 'val1', 'od.dodval2.stval2': 'val2', 'od.dodval3.stval3': 'val3'} + assert not await list_sessions() diff --git a/tiramisu/config.py b/tiramisu/config.py index c9a34a9..e987cea 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -173,7 +173,8 @@ class SubConfig: async def cfgimpl_reset_cache(self, option_bag, - resetted_opts=None): + resetted_opts=None, + ): """reset all settings in cache """ if resetted_opts is None: diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index df518a3..c4e51ee 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -55,6 +55,7 @@ class Base: '_properties', '_has_dependency', '_dependencies', + '_suffixes_dependencies', '__weakref__' ) @@ -99,18 +100,29 @@ class Base: return hasattr(self, '_dependencies') def _get_dependencies(self, - context_od) -> Set[str]: + context_od, + ) -> Set[str]: ret = set(getattr(self, '_dependencies', STATIC_TUPLE)) if context_od and hasattr(context_od, '_dependencies'): # add options that have context is set in calculation return set(context_od._dependencies) | ret return ret + def _get_suffixes_dependencies(self) -> Set[str]: + return getattr(self, '_suffixes_dependencies', STATIC_TUPLE) + def _add_dependency(self, - option) -> None: + option, + is_suffix: bool=False, + ) -> None: + woption = weakref.ref(option) options = self._get_dependencies(None) options.add(weakref.ref(option)) self._dependencies = tuple(options) + if is_suffix: + options = list(self._get_suffixes_dependencies()) + options.append(weakref.ref(option)) + self._suffixes_dependencies = tuple(options) def _impl_set_callback(self, callback: Callable, diff --git a/tiramisu/option/dynoptiondescription.py b/tiramisu/option/dynoptiondescription.py index f341f5c..b79a98f 100644 --- a/tiramisu/option/dynoptiondescription.py +++ b/tiramisu/option/dynoptiondescription.py @@ -66,7 +66,9 @@ class DynOptionDescription(OptionDescription): raise ConfigError(_('suffixes in dynoptiondescription has to be a calculation')) for param in chain(suffixes.params.args, suffixes.params.kwargs.values()): if isinstance(param, ParamOption): - param.option._add_dependency(self) + param.option._add_dependency(self, + is_suffix=True, + ) self._suffixes = suffixes def convert_suffix_to_path(self, diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index c66fb57..3520a52 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -87,11 +87,6 @@ class CacheOptionDescription(BaseOption): if not option.impl_is_symlinkoption(): properties = option.impl_getproperties() if 'force_store_value' in properties: - if __debug__: - if option.issubdyn(): - raise ConfigError(_('the dynoption "{0}" cannot have ' - '"force_store_value" property').format( - option.impl_get_display_name())) force_store_values.append((subpath, option)) if __debug__ and ('force_default_on_freeze' in properties or \ 'force_metaconfig_on_freeze' in properties) and \ @@ -115,7 +110,8 @@ class CacheOptionDescription(BaseOption): self._set_readonly() async def impl_build_force_store_values(self, - config_bag: ConfigBag) -> None: + config_bag: ConfigBag, + ) -> None: if 'force_store_value' not in config_bag.properties: return values = config_bag.context.cfgimpl_get_values() @@ -143,17 +139,37 @@ class CacheOptionDescription(BaseOption): index, False) else: - option_bag = OptionBag() - option_bag.set_option(option, - None, - config_bag) - option_bag.properties = frozenset() - await values._p_.setvalue(config_bag.connection, - subpath, - await values.getvalue(option_bag), - owners.forced, + option_bags = [] + if option.issubdyn(): + dynopt = option.getsubdyn() + rootpath = dynopt.impl_getpath() + subpaths = [rootpath] + option.impl_getpath()[len(rootpath) + 1:].split('.')[1:] + for suffix in await dynopt.get_suffixes(config_bag): + path_suffix = dynopt.convert_suffix_to_path(suffix) + subpath = '.'.join([subp + path_suffix for subp in subpaths]) + doption = option.to_dynoption(subpath, + suffix, + option) + doption_bag = OptionBag() + doption_bag.set_option(doption, + None, + config_bag) + option_bags.append(doption_bag) + else: + option_bag = OptionBag() + option_bag.set_option(option, None, - False) + config_bag) + option_bags.append(option_bag) + for option_bag in option_bags: + option_bag.properties = frozenset() + await values._p_.setvalue(config_bag.connection, + option_bag.path, + await values.getvalue(option_bag), + owners.forced, + None, + False, + ) class OptionDescriptionWalk(CacheOptionDescription): diff --git a/tiramisu/value.py b/tiramisu/value.py index 8fe229c..641e46b 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -242,6 +242,8 @@ class Values: return # calculated value is a new value, so reset cache await option_bag.config_bag.context.cfgimpl_reset_cache(option_bag) + # and manage force_store_value + await self._set_force_value_suffix(option_bag) async def calculate_value(self, option_bag: OptionBag, @@ -361,15 +363,58 @@ class Values: check_error=False) async def _setvalue(self, - option_bag, - value, - owner): + option_bag: OptionBag, + value: Any, + owner: str, + ) -> None: await option_bag.config_bag.context.cfgimpl_reset_cache(option_bag) await self._p_.setvalue(option_bag.config_bag.connection, option_bag.path, value, owner, option_bag.index) + await self._set_force_value_suffix(option_bag) + + async def _set_force_value_suffix(self, + option_bag: OptionBag, + ) -> None: + if 'force_store_value' not in option_bag.config_bag.properties: + return + for woption in option_bag.option._get_suffixes_dependencies(): + option = woption() + force_store_options = [] + async for coption in option.get_children_recursively(None, + None, + option_bag.config_bag, + ): + if 'force_store_value' in coption.impl_getproperties(): + force_store_options.append(coption) + if not force_store_options: + continue + rootpath = option.impl_getpath() + settings = option_bag.config_bag.context.cfgimpl_get_settings() + for suffix in await option.get_suffixes(option_bag.config_bag): + for coption in force_store_options: + subpaths = [rootpath] + coption.impl_getpath()[len(rootpath) + 1:].split('.')[:-1] + path_suffix = option.convert_suffix_to_path(suffix) + subpath = '.'.join([subp + path_suffix for subp in subpaths]) + doption = coption.to_dynoption(subpath, + suffix, + coption, + ) + coption_bag = OptionBag() + coption_bag.set_option(doption, + None, + option_bag.config_bag, + ) + coption_bag.properties = await settings.getproperties(coption_bag) + await self._p_.setvalue(coption_bag.config_bag.connection, + coption_bag.path, + await self.getvalue(coption_bag), + owners.forced, + None, + False, + ) async def _get_modified_parent(self, option_bag: OptionBag) -> Optional[OptionBag]: From 87093da974c47e7d01bed704d462c3d8b3a5b06c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 3 Apr 2021 20:38:08 +0200 Subject: [PATCH 16/16] add test about deepcopy with multi parents --- tests/test_dyn_optiondescription.py | 1 - tests/test_metaconfig.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/test_dyn_optiondescription.py b/tests/test_dyn_optiondescription.py index 62373ca..431e7d0 100644 --- a/tests/test_dyn_optiondescription.py +++ b/tests/test_dyn_optiondescription.py @@ -268,7 +268,6 @@ async def test_prop_dyndescription_force_store_value(): @pytest.mark.asyncio async def test_prop_dyndescription_force_store_value_calculation_prefix(): - print() lst = StrOption('lst', '', ['val1', 'val2'], multi=True) st = StrOption('st', '', Calculation(return_list, Params(ParamSuffix())) , properties=('force_store_value',)) dod = DynOptionDescription('dod', '', [st], suffixes=Calculation(return_list, Params(ParamOption(lst)))) diff --git a/tests/test_metaconfig.py b/tests/test_metaconfig.py index cf216f6..8d6b98e 100644 --- a/tests/test_metaconfig.py +++ b/tests/test_metaconfig.py @@ -1130,6 +1130,39 @@ async def test_meta_properties_meta_deepcopy(): await delete_sessions([meta, meta2]) +@pytest.mark.asyncio +async def test_meta_properties_meta_deepcopy_multi_parent(): + ip_admin_eth0 = NetworkOption('ip_admin_eth0', "ip") + netmask_admin_eth0 = NetmaskOption('netmask_admin_eth0', "mask") + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + conf1 = await Config(interface1, session_id='conf1') + conf2 = await Config(interface1, session_id='conf2') + await conf1.property.read_write() + await conf2.property.read_write() + meta1 = await MetaConfig([conf1, conf2], session_id='meta1') + await meta1.permissive.add('hidden') + await meta1.property.read_write() + + meta2 = await MetaConfig(['name1', 'name2'], optiondescription=interface1, session_id='meta2') + await meta2.config.add(conf1) + + await meta1.option('ip_admin_eth0').value.set('192.168.1.1') + await meta2.option('netmask_admin_eth0').value.set('255.255.255.0') + + assert await meta1.value.dict() == {'ip_admin_eth0': '192.168.1.1', 'netmask_admin_eth0': None} + assert await meta2.value.dict() == {'ip_admin_eth0': None, 'netmask_admin_eth0': '255.255.255.0'} + assert await conf1.value.dict() == {'ip_admin_eth0': '192.168.1.1', 'netmask_admin_eth0': '255.255.255.0'} + assert await conf2.value.dict() == {'ip_admin_eth0': '192.168.1.1', 'netmask_admin_eth0': None} + + copy_meta2 = await conf1.config.deepcopy(session_id='copy_conf1', metaconfig_prefix='copy_') + assert await copy_meta2.config.path() == 'copy_meta2' + copy_meta1 = await copy_meta2.config('copy_meta1') + copy_conf1 = await copy_meta1.config('copy_conf1') + assert await copy_meta2.value.dict() == {'ip_admin_eth0': None, 'netmask_admin_eth0': '255.255.255.0'} + assert await copy_conf1.value.dict() == {'ip_admin_eth0': '192.168.1.1', 'netmask_admin_eth0': '255.255.255.0'} + await delete_sessions([conf1, conf2, meta1, meta2, copy_conf1, copy_meta1, copy_meta2]) + + @pytest.mark.asyncio async def test_meta_properties_submeta_deepcopy(): ip_admin_eth0 = NetworkOption('ip_admin_eth0', "ip", multi=True, default=['192.168.1.1'])