Compare commits

...

84 commits

Author SHA1 Message Date
f68d56017c bump: version 5.2.0a25 → 5.2.0a26 2026-01-29 08:35:56 +01:00
61f90cb530 feat: can unsort data in display_list 2026-01-29 08:35:49 +01:00
c8b7d09b41 bump: version 5.2.0a24 → 5.2.0a25 2026-01-21 08:57:39 +01:00
27e8f0c69f fix: subconfig with follower 2026-01-16 21:05:47 +01:00
50ff511554 bump: version 5.2.0a23 → 5.2.0a24 2026-01-04 19:25:39 +01:00
b55ffadc10 feat: can use forcepermissive, unrestraint or nowarnings for a subconfig 2026-01-04 19:25:30 +01:00
9d094a9676 bump: version 5.2.0a22 → 5.2.0a23 2026-01-01 21:55:18 +01:00
2c091f7a12 feat: name and type for a config 2026-01-01 21:55:10 +01:00
71fbe6dcbd fix: name is mandatory for config inside a metaconfig 2026-01-01 21:52:36 +01:00
cc79c28d40 bump: version 5.2.0a21 → 5.2.0a22 2026-01-01 09:23:07 +01:00
757e3aec6e fix: remove debugging 2026-01-01 09:22:41 +01:00
7af43a1e19 bump: version 5.2.0a20 → 5.2.0a21 2025-12-30 10:46:35 +01:00
e39b8106be fix: set permissive for all option in carry_out_calculation 2025-12-29 11:53:14 +01:00
b7d97275e3 bump: version 5.2.0a19 → 5.2.0a20 2025-12-23 10:20:13 +01:00
27b1bb8294 feat: add forcepermissive() and index(None) + improvment 2025-12-23 10:08:58 +01:00
6b0a0d8e56 bump: version 5.2.0a18 → 5.2.0a19 2025-12-22 19:02:03 +01:00
636aa721dc fix: calculation with leader 2025-12-22 19:01:43 +01:00
01e0d1de5c bump: version 5.2.0a17 → 5.2.0a18 2025-12-22 08:51:17 +01:00
95c62f1122 fix: better error message 2025-12-22 08:51:08 +01:00
fe108b1238 feat: add subconfig in errors 2025-12-19 13:30:30 +01:00
050979f6d3 feat: ParamSelfOption accept dynamic=False 2025-11-26 09:22:42 +01:00
faf694397a feat: do not save disabled force_store_value option 2025-11-25 21:44:04 +01:00
a5870ee2ab fix: force_store_value in dynamic family 2025-11-24 20:06:54 +01:00
dda10f76f8 bump: version 5.2.0a16 → 5.2.0a17 2025-11-03 08:51:30 +01:00
585cda5ffc feat: value modified in parent config is a default value for current config and can get default owner for an option 2025-11-03 08:50:50 +01:00
43d3046e4c bump: version 5.2.0a15 → 5.2.0a16 2025-10-29 12:16:11 +01:00
da8e4ded3c feat: property that make sens + better dependencies 2025-10-29 12:16:04 +01:00
7e8940252b bump: version 5.2.0a14 → 5.2.0a15 2025-10-22 17:14:48 +02:00
47c84895f7 feat: .identifiers can return convert value 2025-10-19 21:44:29 +02:00
d1add3dc5a fix: uncalculated dynamic 2025-10-18 11:50:49 +02:00
3fad16d0ac fix: accept allow_dynoption 2025-10-18 06:31:40 +02:00
e6b3e7c317 fix: identifier could be temporary None 2025-10-18 06:29:32 +02:00
be637e4e31 bump: version 5.2.0a13 → 5.2.0a14 2025-10-16 08:21:22 +02:00
4bd1b8f04e fix: allow_dynoption 2025-10-16 08:10:53 +02:00
6f2f479364 fix: allow_dynoption in group_type() 2025-10-15 11:30:22 +02:00
0513d6677e fix: black 2025-10-15 09:43:56 +02:00
60e259fef2 fix: allow_dynoption in identifiers() 2025-10-15 09:39:22 +02:00
acca0bc040 fix: allow_dynoption for option informations 2025-10-15 09:29:52 +02:00
e0f16b14c7 feat: property.get(uncalculated=True) is now possible for an dynoptiondescription without identifiers 2025-10-15 09:11:33 +02:00
e8921795d3 bump: version 5.2.0a12 → 5.2.0a13 2025-10-10 08:10:33 +02:00
ecbd17915b fix: can unrestraint a already unrestraint config 2025-10-06 21:37:46 +02:00
d44a5ebe32 translate option type 2025-10-05 20:43:42 +02:00
5e0bf84e50 simplify network/boardcast validation 2025-10-05 20:43:23 +02:00
c56cdcfa02 do not display password in error 2025-10-05 20:41:00 +02:00
aa774cbce9 bump: version 5.2.0a11 → 5.2.0a12 2025-09-30 22:03:53 +02:00
b48328d021 fix: avoid infinite loop in rougail 2025-09-30 21:48:37 +02:00
7fca8e9d62 bump: version 5.2.0a10 → 5.2.0a11 2025-09-29 11:02:23 +02:00
9c97f92775 fix: add min_integer and max_integer for intOption 2025-09-23 20:32:24 +02:00
7173a93749 bump: version 5.2.0a9 → 5.2.0a10 2025-09-20 18:26:39 +02:00
e351da2069 feat: better conflict with dynamic name 2025-09-20 18:16:43 +02:00
47c413e60c feat: reactive metaconfig 2025-09-11 22:07:53 +02:00
ac35f2428b fix: add dependency for calculation in choice values 2025-05-23 17:58:35 +02:00
ebc9779173 fix: better port validation with the value '' 2025-05-23 17:56:58 +02:00
379630fc38 bump: version 5.2.0a8 → 5.2.0a9 2025-05-12 09:12:51 +02:00
65b8fb014e fix: upgrade translation 2025-05-12 09:05:06 +02:00
fcbb9c6812 fix: better error message 2025-05-12 08:53:39 +02:00
c14a34f232 bump: version 5.2.0a7 → 5.2.0a8 2025-05-05 08:44:59 +02:00
db1b8dd48a bump: version 5.2.0a6 → 5.2.0a7 2025-05-05 08:42:04 +02:00
5ed902a372 fix: better error message 2025-05-05 08:39:43 +02:00
c54f7cb775 bump: version 5.2.0a5 → 5.2.0a6 2025-04-30 09:12:27 +02:00
3aaebfa34e fix: update translation 2025-04-29 23:02:13 +02:00
1a8ad3d06e fix: better error message 2025-04-29 22:56:34 +02:00
c24b0642a7 feat: ability to redefine ConfigError message 2025-04-27 14:46:22 +02:00
7ecec88881 fix: do not modify the default_multi attribute 2025-04-21 19:45:14 +02:00
3d8eb2c80d bump: version 5.2.0a4 → 5.2.0a5 2025-04-09 21:16:49 +02:00
3fadb1f6c3 fix: version 2025-04-09 21:16:39 +02:00
65b156c47d bump: version 5.2.0a3 → 5.2.0a4 2025-04-09 08:28:06 +02:00
db82dd6d41 fix: better error message 2025-04-09 08:27:38 +02:00
888ba21551 bump: version 5.2.0a2 → 5.2.0a3 2025-03-19 09:58:14 +01:00
b8899c98b1 fix: better errors message 2025-03-19 09:57:03 +01:00
1b640689b8 bump: version 5.2.0a1 → 5.2.0a2 2025-02-13 22:11:53 +01:00
adf94e6b15 feat: an option could be not validate 2025-02-13 22:11:26 +01:00
2f0e1fcb0c bump: version 5.2.0a0 → 5.2.0a1 2025-02-10 09:11:13 +01:00
6e4b22aea2 feat: can get params value easily 2025-02-10 08:45:21 +01:00
90947b5578 feat: ability to resolve domainname 2025-02-07 08:01:59 +01:00
f33713231e feat: better error messages 2025-02-07 07:46:53 +01:00
b443771c30 bump: version 5.1.1a1 → 5.2.0a0 2025-01-04 17:39:41 +01:00
6181b728ad fix: message error improvment 2025-01-04 17:39:25 +01:00
4cba819e0a feat: return index in error message 2025-01-04 17:39:11 +01:00
42471d42b7 feat: option.type can return the symlink type 2025-01-04 17:36:44 +01:00
0c993eddb0 bump: version 5.1.1a0 → 5.1.1a1 2024-12-11 21:58:17 +01:00
22fdabb6c0 fix: option.get now accept allow_dynoption 2024-12-11 21:57:46 +01:00
46a27e3a5c bump: version 5.1.0 → 5.1.1a0 2024-11-25 09:02:41 +01:00
baf1245c7a fix: dynoption could be optional if identifier is unknown 2024-11-25 09:00:00 +01:00
68 changed files with 5974 additions and 4291 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ requires = ["flit_core >=3.8.0,<4"]
[project]
name = "tiramisu"
version = "5.1.0"
version = "5.2.0a26"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md"
description = "an options controller tool"
@ -18,6 +18,8 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Natural Language :: English",
@ -33,5 +35,9 @@ name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "pep621"
version_files = [
"tiramisu/__version__.py",
"pyproject.toml:version"
]
#update_changelog_on_bump = true
changelog_merge_prerelease = true

View file

@ -1,5 +1,4 @@
# from json import dumps, loads
import asyncio
from os import environ
try:
from tiramisu_api import Config
@ -63,3 +62,14 @@ def parse_od_get(dico):
else:
ret[k.path()] = v
return ret
def get_dependencies(option):
ret = []
for a in option.dependencies():
if a.isoptiondescription():
ret.append((a.path(), None))
else:
ret.append((a.path(), a.index()))
ret.sort()
return ret

View file

@ -69,11 +69,11 @@ def test_cache_importation_property():
cfg = Config(od1)
cfg.option('u2').property.add('prop')
export = cfg.property.exportation()
assert cfg.option('u2').property.get() == {'prop'}
assert cfg.option('u2').property.get() == {'validator', 'prop'}
cfg.option('u2').property.add('prop2')
assert cfg.option('u2').property.get() == {'prop', 'prop2'}
assert cfg.option('u2').property.get() == {'validator', 'prop', 'prop2'}
cfg.property.importation(export)
assert cfg.option('u2').property.get() == {'prop'}
assert cfg.option('u2').property.get() == {'validator', 'prop'}
cfg = Config(od1)
# assert not list_sessions()
@ -366,8 +366,8 @@ def test_cache_leader_and_followers():
cfg.value.get()
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
val1_props = []
val1_val1_props = ['empty', 'unique']
val1_val2_props = []
val1_val1_props = ['empty', 'unique', 'validator']
val1_val2_props = ['validator']
global_props = frozenset(global_props)
val1_props = frozenset(val1_props)
val1_val1_props = frozenset(val1_val1_props)
@ -384,7 +384,7 @@ def test_cache_leader_and_followers():
#
cfg.option('val1.val1').value.set([None])
val_val2_props = {idx_val2: (val1_val2_props, None), None: (set(), None)}
compare(settings.get_cached(), {'val1.val1': {None: ({'empty', 'unique'}, None, True)}})
compare(settings.get_cached(), {'val1.val1': {None: ({'validator', 'empty', 'unique'}, None, True)}})
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
cfg.value.get()
#has value
@ -416,8 +416,8 @@ def test_cache_leader_callback():
cfg.value.get()
global_props = ['cache', 'disabled', 'frozen', 'hidden', 'validator', 'warnings', 'force_store_value']
val1_props = []
val1_val1_props = ['empty', 'unique']
val1_val2_props = []
val1_val1_props = ['empty', 'unique', 'validator']
val1_val2_props = ['validator']
global_props = frozenset(global_props)
val1_props = frozenset(val1_props)
val1_val1_props = frozenset(val1_val1_props)
@ -429,7 +429,7 @@ def test_cache_leader_callback():
})
compare(values.get_cached(), {'val1.val1': {None: ([], None)}})
cfg.option('val1.val1').value.set([None])
compare(settings.get_cached(), {'val1.val1': {None: ({'unique', 'empty'}, None, True)}})
compare(settings.get_cached(), {'val1.val1': {None: ({'unique', 'empty', 'validator'}, None, True)}})
compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}})
cfg.value.get()
@ -451,24 +451,24 @@ def test_cache_requires():
settings = cfg._config_bag.context.properties_cache
assert values.get_cached() == {}
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({'validator'}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.option('ip_address_service').value.set('1.1.1.1')
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'activate_service': {None: (True, None)}, 'ip_address_service': {None: ('1.1.1.1', None, True)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: ('1.1.1.1', None)},
'activate_service': {None: (True, None)}})
@ -477,8 +477,8 @@ def test_cache_requires():
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
cfg.value.get()
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set(['disabled']), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({'disabled', "validator"}, None)}})
compare(values.get_cached(), {'activate_service': {None: (False, None)}})
# assert not list_sessions()
@ -499,19 +499,19 @@ def test_cache_global_properties():
settings = cfg._config_bag.context.properties_cache
assert values.get_cached() == {}
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
compare(values.get_cached(), {'ip_address_service': {None: (None, None)},
'activate_service': {None: (True, None)}})
cfg.property.remove('disabled')
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
cfg.property.add('test')
assert cfg.option('ip_address_service').value.get() == None
compare(settings.get_cached(), {'activate_service': {None: (set([]), None)},
'ip_address_service': {None: (set([]), None)}})
compare(settings.get_cached(), {'activate_service': {None: ({"validator"}, None)},
'ip_address_service': {None: ({"validator"}, None)}})
# assert not list_sessions()

View file

@ -162,6 +162,10 @@ def test_choiceoption_calc_opt_function(config_type):
cfg = get_config(cfg, config_type)
assert cfg.option('choice').owner.isdefault()
#
dep = cfg.option('str').dependencies()
assert len(dep) == 1
assert dep[0].path() == 'choice'
#
cfg.option('choice').value.set('val1')
assert cfg.option('choice').owner.get() == owner
#
@ -187,7 +191,6 @@ def test_choiceoption_calc_opt_function_propertyerror():
# assert not list_sessions()
#def test_choiceoption_calc_opt_multi_function(config_type):
def test_choiceoption_calc_opt_multi_function():
# FIXME
config_type = 'tiramisu'

View file

@ -332,7 +332,7 @@ def test_duplicated_option():
g1
#in same OptionDescription
with pytest.raises(ConflictError):
d1 = OptionDescription('od', '', [g1, g1])
OptionDescription('od', '', [g1, g1])
# assert not list_sessions()
@ -346,6 +346,28 @@ def test_duplicated_option_diff_od():
Config(d2)
def test_duplicated_option_diff_od_2():
g1 = IntOption('g1', '', 1)
d1 = OptionDescription('od1', '', [g1])
#in different OptionDescription
d2 = OptionDescription('od2', '', [d1, g1])
d2
with pytest.raises(ConflictError):
Config(d2)
def test_duplicated_option_diff_od_3():
g1 = IntOption('g1', '', 1)
d1 = OptionDescription('od1', '', [g1])
d3 = OptionDescription('od3', '', [g1])
#in different OptionDescription
d2 = OptionDescription('od2', '', [d1, d3])
d4 = OptionDescription('od4', '', [d2])
d4
with pytest.raises(ConflictError):
Config(d4)
def test_cannot_assign_value_to_option_description():
od1 = make_description()
cfg = Config(od1)
@ -380,12 +402,12 @@ def test_prefix_error():
try:
cfg.option('test1').value.set('yes')
except Exception as err:
assert str(err) == _('"{0}" is an invalid {1} for "{2}"').format('yes', _('integer'), 'test1')
assert str(err) == _('"{0}" is an invalid {1} for "{2}", it\'s not an integer').format('yes', _('integer'), 'test1')
try:
cfg.option('test1').value.set('yes')
except Exception as err:
err.prefix = ''
assert str(err) == _('invalid value')
assert str(err) == _('it\'s not an integer')
# assert not list_sessions()

View file

@ -13,12 +13,6 @@ from tiramisu.error import PropertiesOptionError, ValueWarning, ConfigError
import warnings
#def teardown_function(function):
# # test_od_not_list emit a warnings because of doesn't create a Config
# with warnings.catch_warnings(record=True) as w:
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def make_description():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
@ -352,6 +346,8 @@ def test_invalid_option():
PortOption('a', '', allow_zero=False, allow_wellknown=False, allow_registred=False, allow_private=False)
with raises(ValueError):
PortOption('a', '', 'tcp:80')
with raises(ValueError):
PortOption('a', '', '')
NetworkOption('a', '')
with raises(ValueError):
NetworkOption('a', '', 'string')
@ -423,7 +419,7 @@ def test_config_reset():
cfg.owner.set('test')
assert cfg.owner.get() == 'test'
assert not cfg.option('gc.gc2.bool').value.get()
assert not cfg.option('boolop').property.get()
assert cfg.option('boolop').property.get() == frozenset(["validator"])
assert not cfg.option('boolop').permissive.get()
assert not cfg.option('wantref').information.get('info', None)
#
@ -440,7 +436,7 @@ def test_config_reset():
cfg.config.reset()
assert cfg.owner.get() == 'test'
assert not cfg.option('gc.gc2.bool').value.get()
assert not cfg.option('boolop').property.get()
assert cfg.option('boolop').property.get() == {"validator"}
assert not cfg.option('float').permissive.get()
assert not cfg.option('wantref').information.get('info', None)
# assert not list_sessions()

View file

@ -275,3 +275,9 @@ def test_url(config_type):
with pytest.raises(ValueError):
cfg.option('u').value.set('https://FOO.COM:8443')
# assert not list_sessions()
def test_domainname_existence():
DomainnameOption('d', '', 'google.fr', test_existence=True)
with pytest.raises(ValueError):
DomainnameOption('d', '', 'ljijouuuehyfr.com', test_existence=True)

View file

@ -21,6 +21,7 @@ def test_ip(config_type):
cfg.option('a').value.set('192.168.1.0')
cfg.option('a').value.set('88.88.88.88')
cfg.option('a').value.set('0.0.0.0')
cfg.option('a').value.set('2001:db8::1')
if config_type != 'tiramisu-api':
# FIXME
with pytest.raises(ValueError):
@ -148,7 +149,6 @@ def test_network_cidr(config_type):
cfg.option('a').value.set('192.168.1.1')
with pytest.raises(ValueError):
cfg.option('a').value.set('192.168.1.1/24')
with pytest.raises(ValueError):
cfg.option('a').value.set('2001:db00::0/24')
# assert not list_sessions()
@ -197,7 +197,6 @@ def test_broadcast(config_type):
cfg.option('a').value.set(1)
with pytest.raises(ValueError):
cfg.option('a').value.set(2)
with pytest.raises(ValueError):
cfg.option('a').value.set('2001:db8::1')
cfg.option('a').value.set('0.0.0.0')
cfg.option('a').value.set('255.255.255.0')

View file

@ -80,15 +80,15 @@ def test_copy_force_store_value():
assert conf.value.exportation() == {'creole.general.wantref': {None: [True, 'user']}}
assert conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert not list_sessions()
#
#
#def test_copy_force_store_value_metaconfig():
# od1 = make_description()
# meta = MetaConfig([], optiondescription=od1)
# conf = meta.config.new()
# assert meta.property.get() == conf.property.get()
# assert meta.permissive.get() == conf.permissive.get()
# conf.property.read_write()
# assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert meta.value.exportation() == {}
## assert not list_sessions()
def test_copy_force_store_value_metaconfig():
od1 = make_description()
meta = MetaConfig([], optiondescription=od1)
conf = meta.config.new()
assert meta.property.get() == conf.property.get()
assert meta.permissive.get() == conf.permissive.get()
conf.property.read_write()
assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
assert meta.value.exportation() == {}
# assert not list_sessions()

View file

@ -1,7 +1,7 @@
# coding: utf-8
from .autopath import do_autopath
do_autopath()
from .config import parse_od_get
from .config import parse_od_get, get_dependencies
import pytest
from tiramisu.setting import groups, owners
@ -13,7 +13,7 @@ from tiramisu import BoolOption, StrOption, ChoiceOption, IPOption, \
Config, \
Params, ParamOption, ParamValue, ParamIdentifier, ParamSelfOption, ParamDynOption, ParamIndex, ParamSelfInformation, ParamInformation, \
Calculation, calc_value
from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError, ValueOptionError
from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError, ValueOptionError, AttributeOptionError
def display_name(kls, subconfig, with_quote=False) -> str:
@ -28,6 +28,8 @@ def display_name(kls, subconfig, with_quote=False) -> str:
class ConvertDynOptionDescription(DynOptionDescription):
def convert_identifier_to_path(self, identifier):
# remove dot with is illegal
if not identifier:
return identifier
return identifier.replace('.', '')
@ -37,6 +39,18 @@ def return_true(value, param=None, identifier=None):
raise ValueError('no value')
def return_no_dyn(value):
if value in [['val', 'val'], ['yes', 'yes']]:
return
raise ValueError('no value')
def return_no_dyn_properties(value, identifier):
idx = int(identifier)
if idx and not value[idx - 1]:
return 'disabled'
def return_dynval(value='val', identifier=None):
return value
@ -300,23 +314,37 @@ def test_prop_dyndescription():
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval1.st').property.get()) == {'test', "validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {'test', "validator"}
cfg.option('od.dodval2.st').property.add('test2')
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {'test', "validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {'test', 'test2', "validator"}
#
assert set(cfg.option('od.dodval1').property.get()) == set([])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == set()
assert set(cfg.option('od.dodval2').property.get()) == set()
cfg.option('od.dodval1').property.add('test1')
assert set(cfg.option('od.dodval1').property.get()) == set(['test1'])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == {'test1'}
assert set(cfg.option('od.dodval2').property.get()) == set()
cfg.option('od.dodval1').property.remove('test1')
assert set(cfg.option('od.dodval1').property.get()) == set([])
assert set(cfg.option('od.dodval2').property.get()) == set([])
assert set(cfg.option('od.dodval1').property.get()) == set()
assert set(cfg.option('od.dodval2').property.get()) == set()
# assert not list_sessions()
def test_prop_dyndescription_uncalculated():
st = StrOption('st', '', properties=('test',))
od = OptionDescription('od', '', [st], properties=('test_od',))
dod = DynOptionDescription('dod', '', [od], identifiers=Calculation(return_list))
od2 = OptionDescription('od', '', [dod])
cfg = Config(od2)
assert set(cfg.option('dod.od').property.get(uncalculated=True)) == {'test_od'}
assert set(cfg.option('dod.od.st').property.get(uncalculated=True)) == {'test', 'validator'}
with pytest.raises(AttributeOptionError):
set(cfg.option('dod.od').property.get())
with pytest.raises(AttributeOptionError):
set(cfg.option('dod.od.st').property.get())
def test_prop_dyndescription_force_store_value():
st = StrOption('st', '', properties=('force_store_value',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
@ -328,6 +356,17 @@ def test_prop_dyndescription_force_store_value():
# assert not list_sessions()
def test_prop_dyndescription_force_store_value_disabled():
st = StrOption('st', '', properties=('force_store_value', 'disabled'))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert parse_od_get(cfg.value.get()) == {}
# assert not list_sessions()
def test_prop_dyndescription_force_store_value_calculation_prefix():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', Calculation(return_list, Params(ParamIdentifier())) , properties=('force_store_value',))
@ -336,6 +375,7 @@ def test_prop_dyndescription_force_store_value_calculation_prefix():
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert get_dependencies(cfg.option('od.lst')) == [('od.dodval1', None), ('od.dodval2', None)]
assert cfg.option('od.dodval1.st').owner.isdefault() == False
assert cfg.option('od.dodval2.st').owner.isdefault() == False
assert parse_od_get(cfg.value.get()) == {'od.lst': ['val1', 'val2'], 'od.dodval1.st': 'val1', 'od.dodval2.st': 'val2'}
@ -394,6 +434,8 @@ def test_callback_dyndescription_outside1():
od = OptionDescription('od', '', [dod, out])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
#
assert get_dependencies(cfg.option('od.dodval1.st')) == [('od.out', None)]
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val', 'od.dodval2.st': 'val', 'od.out': 'val', 'lst': ['val1', 'val2']}
cfg.option('od.dodval1.st').value.set('val1')
cfg.option('od.dodval2.st').value.set('val2')
@ -417,6 +459,8 @@ def test_callback_dyndescription_outside2():
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': None, 'od.dodval2.st': None, 'od.out': None, 'lst': ['val1', 'val2']}
cfg.option('od.out').value.set('val1')
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val1', 'od.dodval2.st': 'val1', 'od.out': 'val1', 'lst': ['val1', 'val2']}
#
assert get_dependencies(cfg.option('od.out')) == [('od.dodval1.st', None), ('od.dodval2.st', None)]
# assert not list_sessions()
@ -432,6 +476,77 @@ def test_callback_dyndescription_outside3():
assert parse_od_get(cfg.value.get()) == {'od.out': 'val1', 'lst': ['val1', 'val2']}
def test_callback_dyndescription_outside_optional():
lst = StrOption('lst', '', ['val'], multi=True)
st = StrOption('st', '', 'val')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(lst))))
out = StrOption('out', '', Calculation(calc_value, Params(ParamDynOption(st, ['unknown_val'], optional=True))))
od = OptionDescription('od', '', [dod, out])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {'od.dodval.st': 'val', 'od.out': None, 'lst': ['val']}
cfg.option('lst').value.set(['val', 'unknown_val'])
assert parse_od_get(cfg.value.get()) == {'od.dodval.st': 'val', 'od.dodunknown_val.st': 'val', 'od.out': 'val', 'lst': ['val', 'unknown_val']}
# assert not list_sessions()
def test_dyndescription_subdyn():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', 'val1')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
out = StrOption('out', '', Calculation(return_dynval, Params(ParamDynOption(st, ['val1', None]))), multi=True, properties=('notunique',))
dod2 = DynOptionDescription('dod2', '', [dod, out], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod2])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
cfg.property.read_write()
assert cfg.option('od.dod2val1.dodval1.st').value.get() == 'val1'
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dodval1.st').value.get()
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2val1.dod.st').value.get()
assert set(cfg.option('od.dod2val1.dodval1.st').property.get()) == {'validator'}
assert set(cfg.option('od.dod2val1.dodval1.st').property.get(uncalculated=True)) == {'validator'}
assert set(cfg.option('od.dod2.dod.st').property.get(uncalculated=True)) == {'validator'}
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dodval1.st').property.get(uncalculated=True)
assert cfg.option('od.dod2val1.dod.st').property.get(uncalculated=True) == {'validator'}
#
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dod.st').name()
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2val1.dod.st').name()
assert cfg.option('od.dod2.dod.st').name(uncalculated=True) == 'st'
cfg.option('od.dod2val1.dod.st').name(uncalculated=True)
#
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dod.st').path()
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2val1.dod.st').path()
assert cfg.option('od.dod2.dod.st').path(uncalculated=True) == 'od.dod2.dod.st'
assert cfg.option('od.dod2val1.dod.st').path(uncalculated=True) == 'od.dod2.dod.st'
#
assert cfg.option('od.dod2.dod.st').isoptiondescription() is False
assert cfg.option('od.dod2val1.dodval1.st').isoptiondescription() is False
assert cfg.option('od.dod2.dod').isoptiondescription() is True
assert cfg.option('od.dod2val1.dodval1').isoptiondescription() is True
assert cfg.option('od.dod2.dod').isleadership() is False
assert cfg.option('od.dod2val1.dodval1').isleadership() is False
#
assert cfg.option('od.dod2.dod.st').type() == "string"
assert cfg.option('od.dod2val1.dodval1.st').type() == "string"
assert cfg.option('od.dod2.dod').type() == "optiondescription"
assert cfg.option('od.dod2val1.dodval1').type() == "optiondescription"
assert cfg.option('od.dod2.dod').group_type() == "default"
assert cfg.option('od.dod2val1.dodval1').group_type() == "default"
#
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dod').identifiers()
assert cfg.option('od.dod2.dod').identifiers(only_self=True) == ['val1', 'val2']
assert cfg.option('od.dod2val1.dodval1').identifiers() == ['val1', 'val1']
assert cfg.option('od.dod2val1.dodval1').identifiers(only_self=True) == ['val1', 'val2']
def test_callback_dyndescription_subdyn():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', 'val1')
@ -443,6 +558,10 @@ def test_callback_dyndescription_subdyn():
cfg = Config(od2)
cfg.property.read_write()
assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval2.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val2.dodval1.st': 'val1', 'od.dod2val2.dodval2.st': 'val1', 'od.dod2val2.out': ['val1', 'val1'], 'lst': ['val1', 'val2']}
cfg.option('lst').value.set(["val1", "val3"])
assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3']}
cfg.option('lst').value.set(["val1", "val3", None])
assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3', None]}
def test_callback_list_dyndescription():
@ -602,14 +721,14 @@ def test_prop_dyndescription_context():
od = OptionDescription('od', '', [dod, val1])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator", 'test'}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test'}
cfg.option('od.dodval2.st').property.add('test2')
assert set(cfg.option('od.dodval1.st').property.get()) == set(['test'])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator", 'test'}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test', 'test2'}
cfg.option('od.dodval1.st').permissive.add('test')
assert set(cfg.option('od.dodval1.st').property.get()) == set([])
assert set(cfg.option('od.dodval2.st').property.get()) == set(['test', 'test2'])
assert set(cfg.option('od.dodval1.st').property.get()) == {"validator"}
assert set(cfg.option('od.dodval2.st').property.get()) == {"validator", 'test', 'test2'}
# assert not list_sessions()
@ -913,6 +1032,22 @@ def test_requires_dyndescription_in_dyn():
# assert not list_sessions()
def test_requires_dyndescription_in_dyn_hidden():
str1 = StrOption('str1', '', "val", properties=('hidden',))
st = StrOption('st', '', Calculation(calc_value, Params(ParamOption(str1))))
dod = DynOptionDescription('dod', '', [str1, st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert cfg.option('od.dodval1.st').value.get() is 'val'
assert cfg.option('od.dodval2.st').value.get() is 'val'
#
with pytest.raises(PropertiesOptionError):
cfg.option('od.dodval1.str1').value.get()
def calc_value_not_same(param, condition, expected, default, identifier):
if identifier == 'val1':
index = 0
@ -1021,6 +1156,43 @@ def test_validator_dyndescription():
# assert not list_sessions()
def test_validator_param_self_option():
out = StrOption('out', '', 'val')
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st_in = StrOption('st_in', '', Calculation(return_dynval, Params(ParamOption(out))))
st = StrOption('st', '', Calculation(return_dynval, Params(ParamOption(st_in))), validators=[Calculation(return_no_dyn, Params((ParamSelfOption(dynamic=False),)))])
dod = DynOptionDescription('dod', '', [st_in, st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod, val1, out])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert cfg.option('od.dodval1.st').value.get() == 'val'
with pytest.raises(ValueError):
cfg.option('od.dodval1.st').value.set('no')
cfg.option('od.out').value.set('yes')
def test_properties_param_self_option():
out = StrOption('out', '', 'val')
val1 = StrOption('val1', '', ["0", "1", "2"], multi=True)
disabled_property = Calculation(return_no_dyn_properties, Params((ParamSelfOption(dynamic=False), ParamIdentifier())))
st = StrOption('st', '', None, properties=(disabled_property,))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
od = OptionDescription('od', '', [dod, val1, out])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert cfg.option('od.dod0.st').value.get() is None
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod1.st').value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod2.st').value.get()
cfg.option('od.dod0.st').value.set('val')
assert cfg.option('od.dod0.st').value.get() == 'val'
assert cfg.option('od.dod1.st').value.get() is None
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod2.st').value.get()
def test_makedict_dyndescription_context():
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '')
@ -1177,6 +1349,8 @@ def test_leadership_dyndescription():
cfg = Config(od1)
owner = cfg.owner.get()
#
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert parse_od_get(cfg.value.get()) == {'od.stval2.st1.st1': [], 'od.stval1.st1.st1': []}
assert cfg.option('od.stval1.st1.st1').value.get() == []
assert cfg.option('od.stval2.st1.st1').value.get() == []
@ -1236,6 +1410,9 @@ def test_leadership_dyndescription_force_store_value_leader():
od1 = OptionDescription('od', '', [od])
cfg = Config(od1)
cfg.property.read_write()
#
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert cfg.option('od.stval1.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval2.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval1.st1.st2', 0).owner.isdefault() == True
@ -1696,16 +1873,6 @@ def test_leadership_callback_samegroup_dyndescription():
# assert not list_sessions()
def test_invalid_conflict_dyndescription():
st = StrOption('st', '')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
dodinvalid = StrOption('dodinvalid', '')
dod, dodinvalid
with pytest.raises(ConflictError):
OptionDescription('od', '', [dod, dodinvalid])
# assert not list_sessions()
def test_leadership_default_multi_dyndescription4():
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True, default_multi='no')
@ -2051,6 +2218,10 @@ def test_leadership_dyndescription_convert():
assert cfg.option('od.stval2.st1.st1').value.get() == []
assert cfg.option('od.stval1.st1.st1').owner.isdefault()
assert cfg.option('od.stval2.st1.st1').owner.isdefault()
assert cfg.option('od.stval2.st1.st1').identifiers() == ["val.2"]
assert cfg.option('od.stval2.st1.st1').identifiers(convert=True) == ["val2"]
assert cfg.option('od.stval2').identifiers(only_self=True) == ["val.1", "val.2"]
assert cfg.option('od.stval2').identifiers(only_self=True, convert=True) == ["val1", "val2"]
#
cfg.option('od.stval1.st1.st1').value.set(['yes'])
assert parse_od_get(cfg.value.get()) == {'od.stval1.st1.st1': [{'od.stval1.st1.st1': 'yes', 'od.stval1.st1.st2': None}], 'od.stval2.st1.st1': []}
@ -2095,6 +2266,23 @@ def test_leadership_dyndescription_convert():
# assert not list_sessions()
def test_leadership_dyndescription_convert_2():
identifier = StrOption("identifier", "", ['val.1', 'val.2', None], multi=True)
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True)
stm = Leadership('st1', '', [st1, st2])
st = ConvertDynOptionDescription('st', '', [stm], identifiers=Calculation(calc_value, Params((ParamOption(identifier),))))
od = OptionDescription('od', '', [st])
od1 = OptionDescription('od', '', [od, identifier])
cfg = Config(od1)
owner = cfg.owner.get()
#
assert cfg.option('od.stval2.st1.st1').identifiers() == ["val.2"]
assert cfg.option('od.stval2.st1.st1').identifiers(convert=True) == ["val2"]
assert cfg.option('od.stval2').identifiers(only_self=True) == ["val.1", "val.2"]
assert cfg.option('od.stval2').identifiers(only_self=True, convert=True) == ["val1", "val2"]
def test_leadership_callback_samegroup_dyndescription_convert():
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True)
@ -2859,3 +3047,86 @@ def test_callback_list_dyndescription_information_not_list():
cfg.information.set('identifier', 'ival3')
assert cfg.option('od.dodival3.st').value.get() == ['ival3', 'val2']
# assert not list_sessions()
def test_dynoption_not_duplicate():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val1"])
dod2 = DynOptionDescription('od_', '', [st2], identifiers=["val2"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {
'od.od_val1.st': None,
'od.od_val2.st': None,
}
def test_dynoption_duplicate_1():
st = StrOption('st', '', "val1")
od = OptionDescription('od_val', '', [st])
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od1 = OptionDescription('od', '', [od, dod])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_2():
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
st = StrOption('st', '', "val1")
od = OptionDescription('od_val', '', [st])
od1 = OptionDescription('od', '', [dod, od])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_3():
st = StrOption('od_val', '')
st2 = StrOption('st', '', "val2")
dod = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od1 = OptionDescription('od', '', [dod, st])
od2 = OptionDescription('od', '', [od1])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_4():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val"])
dod2 = DynOptionDescription('od_', '', [st2], identifiers=["val"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()
def test_dynoption_duplicate_5():
st = StrOption('st', '')
st2 = StrOption('st', '')
dod = DynOptionDescription('od_', '', [st], identifiers=["val"])
dod2 = DynOptionDescription('od', '', [st2], identifiers=["_val"])
od = OptionDescription('od', '', [dod, dod2])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
with pytest.raises(ConflictError):
cfg.value.get()
with pytest.raises(ConflictError):
cfg.option('od.od_val').value.get()

View file

@ -165,15 +165,15 @@ def test_force_store_value():
cfg = Config(od1)
compare(cfg.value.exportation(), {})
cfg.property.read_write()
compare(cfg.value.exportation(), {'wantref': {None: [False, 'forced']}, 'wantref2': {None: [False, 'forced']}, 'wantref3': {None: [[False], 'forced']}})
compare(cfg.value.exportation(), {'wantref3': {None: [[False], 'forced']}})
cfg.option('bool').value.set(False)
cfg.option('wantref').value.set(True)
cfg.option('bool').value.reset()
compare(cfg.value.exportation(), {'wantref': {None: [True, 'user']}, 'wantref2': {None: [False, 'forced']}, 'wantref3': {None: [[False], 'forced']}})
compare(cfg.value.exportation(), {'wantref': {None: [True, 'user']}, 'wantref3': {None: [[False], 'forced']}})
cfg.option('bool').value.set(False)
cfg.option('wantref').value.reset()
cfg.option('bool').value.reset()
compare(cfg.value.exportation(), {'wantref': {None: [False, 'forced']}, 'wantref2': {None: [False, 'forced']}, 'wantref3': {None: [[False], 'forced']}})
compare(cfg.value.exportation(), {'wantref': {None: [False, 'forced']}, 'wantref3': {None: [[False], 'forced']}})
# assert not list_sessions()

View file

@ -6,7 +6,7 @@ import pytest
from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, IPOption, NetworkOption, NetmaskOption, \
StrOption, OptionDescription, Leadership, Config, Calculation, ParamValue, calc_value, Params
StrOption, OptionDescription, Leadership, Config, Calculation, ParamValue, ParamOption, calc_value, Params, submulti
from tiramisu.error import LeadershipError, PropertiesOptionError, ConfigError
@ -200,6 +200,24 @@ def test_groups_is_leader(config_type):
# assert not list_sessions()
def test_leader_follower_index():
ip = StrOption('ip', "", ['val1', 'val2'], multi=True)
netmask = StrOption('netmask', "", multi=True, default_multi='value')
interface1 = Leadership('leadership', '', [ip, netmask])
od1 = OptionDescription('od', '', [interface1])
cfg = Config(od1)
assert cfg.option('leadership.ip').value.get() == ['val1', 'val2']
with pytest.raises(ConfigError):
cfg.option('leadership.ip').index(0)
follower = cfg.option('leadership.netmask')
with pytest.raises(ConfigError):
follower.value.get()
assert follower.index(0).value.get() == 'value'
assert follower.index(0).index(None).index() is None
with pytest.raises(ConfigError):
follower.index(0).index(None).value.get()
def test_leader_list(config_type):
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", ['val1'], multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, default_multi='value')
@ -392,6 +410,9 @@ def test_groups_with_leader_hidden_in_config():
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
cfg.forcepermissive.option('ip_admin_eth0.netmask_admin_eth0').index(0).value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0').index(0).value.get()
assert parse_od_get(cfg.value.get()) == {}
# assert not list_sessions()
@ -788,6 +809,19 @@ def test_values_with_leader_and_followers_leader_pop():
# assert not list_sessions()
def test_values_with_leader_and_followers_leader_pop_default_value():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", default=["192.168.230.145", "192.168.230.146"], multi=True, properties=('notunique',))
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", default_multi="255.255.255.0", multi=True)
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('toto', '', [interface1])
cfg = Config(od1)
cfg.property.read_write()
cfg.option('ip_admin_eth0.ip_admin_eth0').value.pop(0)
compare(cfg.value.exportation(), {'ip_admin_eth0.ip_admin_eth0': {None: [['192.168.230.146'], 'user']}})
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == ["192.168.230.146"]
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == '255.255.255.0'
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',))
@ -796,7 +830,7 @@ def test_follower_unique():
cfg = Config(od1)
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 cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get()
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == {"validator"}
# assert not list_sessions()
@ -1042,7 +1076,7 @@ def test_follower_force_store_value_reset():
# assert not list_sessions()
#def test_follower_properties():
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])
@ -1093,3 +1127,37 @@ def test_leader_forbidden_properties_callback(config_type):
cfg = Config(od1)
with pytest.raises(LeadershipError):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
def test_follower_value_not_list():
ip_admin_eth0 = IPOption('ip_admin_eth0', "ip réseau autorisé", multi=True, default=['1.1.1.1'])
netmask_admin_eth0 = NetmaskOption('netmask_admin_eth0', "masque du sous-réseau", default_multi='255.255.255.0', multi=True, properties=('force_store_value',))
interface0 = Leadership('interface0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('od', '', [interface0])
od2 = OptionDescription('root', '', [od1])
cfg = Config(od2)
cfg.property.read_write()
with pytest.raises(ValueError):
cfg.option('od.interface0.ip_admin_eth0').value.set(None)
def test_default_calc():
var1 = StrOption('var1', "", multi=True, default=['leader1', 'leader2'], properties=frozenset({"mandatory",}))
var2 = StrOption('var2', "", default_multi=[Calculation(calc_value, Params((ParamOption(var1))))], multi=submulti, properties=frozenset({"mandatory",}))
leader = Leadership('interface0', '', [var1, var2])
od1 = OptionDescription('od', '', [leader])
od2 = OptionDescription('root', '', [od1])
cfg = Config(od2)
assert parse_od_get(cfg.value.get()) == {'od.interface0.var1': [{'od.interface0.var1': 'leader1', 'od.interface0.var2': ['leader1']}, {'od.interface0.var1': 'leader2', 'od.interface0.var2': ['leader2']}]}
def test_default_set_calc():
var1 = StrOption('var1', "", multi=True, properties=frozenset({"mandatory", "novalidator", "force_store_value"}))
var2 = StrOption('var2', "", default_multi=[Calculation(calc_value, Params((ParamOption(var1))))], multi=submulti, properties=frozenset({"mandatory",}))
leader = Leadership('interface0', '', [var1, var2])
od1 = OptionDescription('od', '', [leader])
od2 = OptionDescription('root', '', [od1])
cfg = Config(od2)
cfg.property.read_write()
cfg.option('od.interface0.var1').value.set(Calculation(calc_value, Params(ParamValue(['leader1', 'leader2']))))
assert parse_od_get(cfg.value.get()) == {'od.interface0.var1': [{'od.interface0.var1': 'leader1', 'od.interface0.var2': ['leader1']}, {'od.interface0.var1': 'leader2', 'od.interface0.var2': ['leader2']}]}

View file

@ -13,8 +13,6 @@ from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import groups
#def teardown_function(function):
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def is_mandatory(variable):
return True
@ -688,25 +686,25 @@ def return_list(val=None, identifier=None):
return ['val1', 'val2']
#def test_mandatory_dyndescription():
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
# od = OptionDescription('od', '', [dod])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
#
#
#def test_mandatory_dyndescription_context():
# val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
# od = OptionDescription('od', '', [dod, val1])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription():
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription_context():
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
od = OptionDescription('od', '', [dod, val1])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_callback_leader_and_followers_leader():

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -15,76 +15,76 @@ def make_metaconfig():
return MetaConfig([], optiondescription=od2, name='metacfg1')
#def test_multi_parents_path():
# """
# metacfg1 (1) ---
# | -- cfg1
# metacfg2 (2) ---
# """
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert metacfg1.config.path() == 'metacfg1'
# assert metacfg2.config.path() == 'metacfg2'
# assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
#
#
#def test_multi_parents_path_same():
# """
# --- metacfg2 (1) ---
# metacfg1 --| | -- cfg1
# --- metacfg3 (2) ---
# """
# metacfg1 = make_metaconfig()
# metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
# metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
# cfg1 = metacfg2.config.new(type='config', name="cfg1")
# metacfg3.config.add(cfg1)
# #
# assert metacfg2.config.path() == 'metacfg1.metacfg2'
# assert metacfg3.config.path() == 'metacfg1.metacfg3'
# assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
# metacfg1.option('od1.i1').value.set(1)
# metacfg3.option('od1.i1').value.set(2)
# assert cfg1.option('od1.i1').value.get() == 1
# orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
# deep = orideep
# while True:
# try:
# children = list(deep.config.list())
# except:
# break
# assert len(children) < 2
# deep = children[0]
# assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
# assert cfg1.option('od1.i1').value.get() == 1
#
#
#
#def test_multi_parents_value():
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert cfg1.option('od1.i1').value.get() == None
# assert cfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i3').value.get() == None
# #
# assert metacfg1.option('od1.i1').value.get() == None
# assert metacfg1.option('od1.i2').value.get() == 1
# assert metacfg1.option('od1.i3').value.get() == None
# #
# assert metacfg2.option('od1.i1').value.get() == None
# assert metacfg2.option('od1.i2').value.get() == 1
# assert metacfg2.option('od1.i3').value.get() == None
# #
# metacfg1.option('od1.i3').value.set(3)
# assert metacfg1.option('od1.i3').value.get() == 3
# assert cfg1.option('od1.i3').value.get() == 3
# assert metacfg2.option('od1.i2').value.get() == 1
# #
# metacfg2.option('od1.i2').value.set(4)
# assert metacfg2.option('od1.i2').value.get() == 4
# assert metacfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i2').value.get() == 4
def test_multi_parents_path():
"""
metacfg1 (1) ---
| -- cfg1
metacfg2 (2) ---
"""
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert metacfg1.config.path() == 'metacfg1'
assert metacfg2.config.path() == 'metacfg2'
assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
def test_multi_parents_path_same():
"""
--- metacfg2 (1) ---
metacfg1 --| | -- cfg1
--- metacfg3 (2) ---
"""
metacfg1 = make_metaconfig()
metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
cfg1 = metacfg2.config.new(type='config', name="cfg1")
metacfg3.config.add(cfg1)
#
assert metacfg2.config.path() == 'metacfg1.metacfg2'
assert metacfg3.config.path() == 'metacfg1.metacfg3'
assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
metacfg1.option('od1.i1').value.set(1)
metacfg3.option('od1.i1').value.set(2)
assert cfg1.option('od1.i1').value.get() == 1
orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
deep = orideep
while True:
try:
children = list(deep.config.list())
except:
break
assert len(children) < 2
deep = children[0]
assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
assert cfg1.option('od1.i1').value.get() == 1
def test_multi_parents_value():
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert cfg1.option('od1.i1').value.get() == None
assert cfg1.option('od1.i2').value.get() == 1
assert cfg1.option('od1.i3').value.get() == None
#
assert metacfg1.option('od1.i1').value.get() == None
assert metacfg1.option('od1.i2').value.get() == 1
assert metacfg1.option('od1.i3').value.get() == None
#
assert metacfg2.option('od1.i1').value.get() == None
assert metacfg2.option('od1.i2').value.get() == 1
assert metacfg2.option('od1.i3').value.get() == None
#
metacfg1.option('od1.i3').value.set(3)
assert metacfg1.option('od1.i3').value.get() == 3
assert cfg1.option('od1.i3').value.get() == 3
assert metacfg2.option('od1.i2').value.get() == 1
#
metacfg2.option('od1.i2').value.set(4)
assert metacfg2.option('od1.i2').value.get() == 4
assert metacfg1.option('od1.i2').value.get() == 1
assert cfg1.option('od1.i2').value.get() == 4

View file

@ -13,7 +13,7 @@ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _
from .config import config_type, get_config, parse_od_get
from .config import config_type, get_config, parse_od_get, get_dependencies
def return_val():
@ -693,6 +693,7 @@ def test_callback_leader_and_followers_leader(config_type):
cfg = Config(od1)
cfg.property.read_write()
cfg = get_config(cfg, config_type)
assert [(a.path(), a.index()) for a in cfg.option('val1').dependencies()] == [('val2.val2', None)]
assert cfg.option('val1').value.get() == ['val']
assert cfg.option('val2.val2').value.get() == ['val']
#
@ -783,10 +784,29 @@ def test_callback_leader_and_followers_leader2(config_type):
cfg = Config(od1)
cfg.property.read_write()
cfg = get_config(cfg, config_type)
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None)] #, ('val2.val2', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)] #, ('val2.val3', 0)]
#
cfg.option('val1.val1').value.set(['val'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
assert cfg.option('val1.val4', 0).value.get() == 'val2'
assert cfg.option('val1.val3', 0).value.get() == 'val2'
assert cfg.option('val1.val2', 0).value.get() == 'val2'
#
cfg.option('val1.val1').value.set(['val1', 'val2'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 1)) == [('val1', None), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 1)) == [('val1', None), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
# assert not list_sessions()
@ -945,7 +965,7 @@ def test_consistency_leader_and_followers_leader_mandatory_transitive():
try:
cfg.option('val1.val2', 0).value.get()
except PropertiesOptionError as error:
assert str(error) == str(_('cannot access to {0} {1} because has {2} {3}').format('option', '"val2"', _('property'), '"disabled"'))
assert str(error) == str(_('cannot access to {0} {1} at index "{2}" because has {3} {4}').format('option', '"val2"', 0, _('property'), '"disabled"'))
else:
raise Exception('must raises')
assert list(cfg.value.mandatory()) == []
@ -1499,6 +1519,7 @@ def test_leadership_callback_description(config_type):
cfg.option('od.st.st1.st2', 0).value.set('yes')
assert cfg.option('od.st.st1.st1').owner.get() == owner
assert cfg.option('od.st.st1.st2', 0).owner.get() == owner
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None)]
# assert not list_sessions()
@ -1515,6 +1536,8 @@ def test_leadership_callback_outside(config_type):
owner = cfg.owner.get()
cfg.option('od.st.st1.st1').value.set(['yes'])
assert parse_od_get(cfg.value.get()) == {'od.st.st1.st1': [{'od.st.st1.st1': 'yes', 'od.st.st1.st2': 'val2'}], 'od.st.st3': ['val2']}
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None), ('od.st.st3', None,)]
## assert not list_sessions()

View file

@ -154,8 +154,8 @@ def test_force_default_on_freeze_multi():
# with pytest.raises(ConfigError):
# Config(od1)
# assert not list_sessions()
#
#
#def test_force_metaconfig_on_freeze_leader():
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
# dummy2 = BoolOption('dummy2', 'Test string option', multi=True)

View file

@ -64,15 +64,16 @@ def test_hidden_owner():
od1 = OptionDescription('tiramisu', '', [gcdummy])
cfg = Config(od1)
cfg.property.read_write()
#with pytest.raises(PropertiesOptionError):
# cfg.forcepermissive.option('dummy').owner.get()
#with pytest.raises(PropertiesOptionError):
# cfg.option('dummy').owner.isdefault()
#with pytest.raises(PropertiesOptionError):
# cfg.forcepermissive.option('dummy').owner.isdefault()
with pytest.raises(PropertiesOptionError):
cfg.option('dummy').value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('dummy').owner.isdefault()
cfg.permissive.add('hidden')
cfg.forcepermissive.option('dummy').value.get()
cfg.forcepermissive.option('dummy').owner.isdefault()
option = cfg.option("dummy")
option.forcepermissive.value.get()
option.forcepermissive.owner.isdefault()
# assert not list_sessions()

View file

@ -202,10 +202,10 @@ def test_property_get_unique_empty():
od1 = OptionDescription("options", "", [s, s2, s3, s4])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('string').property.get() == {'empty', 'unique'}
assert cfg.option('string2').property.get() == {'empty', 'notunique'}
assert cfg.option('string3').property.get() == {'unique', 'notempty'}
assert cfg.option('string4').property.get() == {'notunique', 'notempty'}
assert cfg.option('string').property.get() == {"validator", 'empty', 'unique'}
assert cfg.option('string2').property.get() == {"validator", 'empty', 'notunique'}
assert cfg.option('string3').property.get() == {"validator", 'unique', 'notempty'}
assert cfg.option('string4').property.get() == {"validator", 'notunique', 'notempty'}
# assert not list_sessions()
@ -220,7 +220,7 @@ def test_property_only_raises():
od1 = OptionDescription("options", "", [s, intoption, stroption])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('str').property.get() == {'empty', 'unique'}
assert cfg.option('str').property.get() == {'empty', 'unique', 'validator'}
assert cfg.option('str').property.get(only_raises=True) == set()
# assert not list_sessions()
@ -569,23 +569,23 @@ def test_access_by_get_whith_hide():
def test_append_properties():
od1 = make_description()
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
cfg.option('gc.dummy').property.add('test')
assert cfg.option('gc.dummy').property.get() == {'test'}
assert cfg.option('gc.dummy').property.get() == {'test', "validator"}
with pytest.raises(ConfigError):
cfg.option('gc.dummy').property.add('force_store_value')
assert cfg.option('gc.dummy').property.get() == {'test'}
assert cfg.option('gc.dummy').property.get() == {'test', "validator"}
# assert not list_sessions()
def test_reset_properties():
od1 = make_description()
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
cfg.option('gc.dummy').property.add('frozen')
assert cfg.option('gc.dummy').property.get() == {'frozen'}
assert cfg.option('gc.dummy').property.get() == {"validator", 'frozen'}
cfg.option('gc.dummy').property.reset()
assert cfg.option('gc.dummy').property.get() == set()
assert cfg.option('gc.dummy').property.get() == {"validator"}
# assert not list_sessions()
@ -594,7 +594,7 @@ def test_properties_cached():
od1 = OptionDescription("opt", "", [OptionDescription("sub", "", [b1])])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.option('sub.b1').property.get() == {'test'}
assert cfg.option('sub.b1').property.get() == {'test', "validator"}
# assert not list_sessions()
@ -603,9 +603,9 @@ def test_append_properties_force_store_value():
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.option('gc.dummy').property.get() == {'force_store_value'}
assert cfg.option('gc.dummy').property.get() == {'force_store_value', "validator"}
cfg.option('gc.dummy').property.add('test')
assert cfg.option('gc.dummy').property.get() == {'force_store_value', 'test'}
assert cfg.option('gc.dummy').property.get() == {'force_store_value', 'test', "validator"}
# assert not list_sessions()
@ -818,27 +818,27 @@ def test_reset_properties_force_store_value():
# assert not list_sessions()
#def test_importation_force_store_value():
# gcdummy = BoolOption('dummy', 'dummy', default=False,
# properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# config1 = Config(od1)
# assert config1.value.exportation() == {}
# config1.property.add('frozen')
# assert config1.value.exportation() == {}
# config1.property.add('force_store_value')
# assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# exportation = config1.property.exportation()
# config2 = Config(od1)
# assert config2.value.exportation() == {}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
## assert not list_sessions()
#
#
def test_importation_force_store_value():
gcdummy = BoolOption('dummy', 'dummy', default=False,
properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
config1 = Config(od1)
assert config1.value.exportation() == {}
config1.property.add('frozen')
assert config1.value.exportation() == {}
config1.property.add('force_store_value')
assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
exportation = config1.property.exportation()
config2 = Config(od1)
assert config2.value.exportation() == {}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# assert not list_sessions()
def test_set_modified_value():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
@ -920,18 +920,57 @@ def test_set_modified_value():
# assert not list_sessions()
#def test_none_is_not_modified():
# gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
# gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# cfg = Config(od1)
# assert cfg.value.exportation() == {}
# cfg.property.read_write()
# assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
## assert not list_sessions()
#
#
def test_none_is_not_modified():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.value.exportation() == {}
cfg.property.read_write()
assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
# assert not list_sessions()
def test_force_store_value_disabled_value():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value', 'disabled'))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.value.exportation() == {}
cfg.option('gc.dummy1').permissive.add('disabled')
assert cfg.option('gc.dummy1').value.get() == 'str'
# do not export
assert cfg._config_bag.context.get_values()._values == {None: {None: [None, 'user']},'gc.dummy1': {None: ['str', 'forced']}}
def test_force_store_value_disabled_owner():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value', 'disabled'))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.value.exportation() == {}
cfg.option('gc.dummy1').permissive.add('disabled')
assert cfg.option('gc.dummy1').owner.get() == owners.forced
assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
def test_force_store_value_disabled_exportation():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value', 'disabled'))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
cfg.property.read_write()
assert cfg.value.exportation() == {}
cfg.option('gc.dummy1').permissive.add('disabled')
assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
def test_pprint():
msg_error = _("cannot access to {0} {1} because has {2} {3}")
msg_is_not = _('the value of "{0}" is not {1}')

View file

@ -6,7 +6,7 @@ import pytest
from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \
IntOption, OptionDescription, Leadership, Config, Params, ParamValue, ParamOption, \
ParamSelfOption, ParamIndex, ParamInformation, ParamSelfInformation, ParamSelfOption, Calculation, \
ParamSelfOption, ParamIndex, ParamInformation, ParamSelfInformation, Calculation, \
valid_ip_netmask, valid_network_netmask, \
valid_in_network, valid_broadcast, valid_not_equal
from tiramisu.setting import groups
@ -34,7 +34,7 @@ def return_val(value, param=None):
def return_if_val(value):
if value != 'val':
if value not in ['val', 'val_not_raise']:
raise ValueError('test error')
@ -121,6 +121,34 @@ def test_validator(config_type):
# assert not list_sessions()
def test_validator_no_validation(config_type):
opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params(ParamSelfOption()))], default='val', properties=frozenset(['novalidator']))
opt2 = StrOption('opt2', '', validators=[Calculation(return_false, Params(ParamSelfOption()))], properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1, opt2])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
assert cfg.option('opt1').value.get() == 'val'
assert cfg.option('opt2').value.valid() is True
cfg.option('opt2').value.set('val')
def test_validator_no_validation2(config_type):
opt1 = StrOption('opt1', '', properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
cfg.option('opt1').value.set(1)
assert cfg.option('opt1').value.get() == 1
def test_validator_no_validation3(config_type):
opt1 = StrOption('opt1', '', 1, properties=frozenset(['novalidator']))
od1 = OptionDescription('root', '', [opt1])
cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type)
assert cfg.option('opt1').value.get() == 1
def test_validator_not_valid(config_type):
with pytest.raises(ValueError):
StrOption('not_a_list', '', validators=Calculation(return_true, Params(ParamSelfOption())), default='val')
@ -313,6 +341,13 @@ def test_validator_multi(config_type):
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val', 'val1'])
assert len(w) == 1
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val1', 'val2'])
assert len(w) == 2
with warnings.catch_warnings(record=True) as w:
# same value twice
cfg.option('opt1').value.set(['val', 'val', 'val_not_raise'])
assert len(w) == 2
# assert not list_sessions()

View file

@ -22,7 +22,16 @@ def test_forcepermissive_and_unrestraint(config_type):
cfg_ori.property.read_write()
cfg = get_config(cfg_ori, config_type)
with pytest.raises(ConfigError):
cfg_ori.forcepermissive.add('disabled')
cfg_ori.unrestraint.forcepermissive
def test_unrestraint_and_unrestraint(config_type):
od1 = make_description()
cfg_ori = Config(od1)
cfg_ori.property.read_write()
cfg_ori.property.read_write()
cfg = get_config(cfg_ori, config_type)
cfg_ori.unrestraint.unrestraint
def test_permissive(config_type):

View file

@ -426,9 +426,9 @@ def test_requires_transitive_unrestraint(config_type):
#
if config_type == 'tiramisu-api':
cfg.send()
assert cfg_ori.option('activate_service_web').property.get() == {'disabled'}
assert cfg_ori.option('activate_service_web').property.get() == {'disabled', "validator"}
# FIXME assert cfg_ori.unrestraint.option('ip_address_service_web').property.get() == {'disabled'}
assert cfg_ori.option('ip_address_service_web').property.get() == {'disabled'}
assert cfg_ori.option('ip_address_service_web').property.get() == {'disabled', "validator"}
# assert not list_sessions()

View file

@ -146,8 +146,8 @@ def test_slots_option_readonly():
with pytest.raises(AttributeError):
q._requires = 'q'
# assert not list_sessions()
#
#
#def test_slots_description():
# # __slots__ for OptionDescription should be complete for __getattr__
# slots = set()

View file

@ -528,43 +528,43 @@ def test_submulti_unique():
# assert not list_sessions()
#def test_multi_submulti_meta():
# multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg')
# cfg.property.read_write()
# cfg2 = Config(od1)
# cfg2.property.read_write()
# meta = MetaConfig([cfg, cfg2])
# meta.property.read_write()
# meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions()
#
#
#def test_multi_submulti_meta_no_cache():
# multi = StrOption('multi', '', multi=submulti)
# multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg')
# cfg.property.read_write()
# cfg2 = Config(od1)
# cfg.property.read_write()
# meta = MetaConfig([cfg, cfg2])
# meta.property.read_write()
# meta.property.remove('cache')
# meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions()
def test_multi_submulti_meta():
multi = StrOption('multi', '', multi=submulti)
od1 = OptionDescription('od', '', [multi])
cfg = Config(od1, name='cfg')
cfg.property.read_write()
cfg2 = Config(od1, name="cfg2")
cfg2.property.read_write()
meta = MetaConfig([cfg, cfg2])
meta.property.read_write()
meta.option('multi').value.set([['val']])
assert meta.option('multi').value.get() == [['val']]
newcfg = meta.config('cfg')
newcfg.option('multi').value.set([['val', None]])
assert cfg.option('multi').value.get() == [['val', None]]
newcfg = meta.config('cfg')
assert newcfg.option('multi').value.get() == [['val', None]]
assert meta.option('multi').value.get() == [['val']]
# assert not list_sessions()
def test_multi_submulti_meta_no_cache():
multi = StrOption('multi', '', multi=submulti)
multi = StrOption('multi', '', multi=submulti)
od1 = OptionDescription('od', '', [multi])
cfg = Config(od1, name='cfg')
cfg.property.read_write()
cfg2 = Config(od1, name="cfg2")
cfg.property.read_write()
meta = MetaConfig([cfg, cfg2])
meta.property.read_write()
meta.property.remove('cache')
meta.option('multi').value.set([['val']])
assert meta.option('multi').value.get() == [['val']]
newcfg = meta.config('cfg')
newcfg.option('multi').value.set([['val', None]])
assert cfg.option('multi').value.get() == [['val', None]]
newcfg = meta.config('cfg')
assert newcfg.option('multi').value.get() == [['val', None]]
assert meta.option('multi').value.get() == [['val']]
# assert not list_sessions()

View file

@ -28,6 +28,8 @@ def test_symlink_option(config_type):
assert cfg.option('c').issymlinkoption()
assert cfg.option('s1.b').type() == 'boolean'
assert cfg.option('c').type() == 'boolean'
assert cfg.option('s1.b').type(only_self=True) == 'boolean'
assert cfg.option('c').type(only_self=True) == 'symlink'
assert cfg.option('s1.b').value.get() is False
cfg.option("s1.b").value.set(True)
cfg.option("s1.b").value.set(False)
@ -157,7 +159,7 @@ def test_symlink_getproperties():
od1 = OptionDescription('opt', '', [boolopt, linkopt])
cfg = Config(od1)
cfg.property.read_write()
assert boolopt.impl_getproperties() == linkopt.impl_getproperties() == {'test'}
assert boolopt.impl_getproperties() == linkopt.impl_getproperties() == {'test', "validator"}
# assert boolopt.impl_has_callback() == linkopt.impl_has_callback() == False
# assert not list_sessions()

View file

@ -1,4 +1,4 @@
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -42,6 +42,7 @@ from .error import ConfigError
from .api import Config, MetaConfig, GroupConfig, MixConfig
from .option import __all__ as all_options
from .setting import owners, groups, undefined
from .__version__ import __version__
allfuncs = [
@ -76,4 +77,3 @@ allfuncs.extend(all_options)
del all_options
__all__ = tuple(allfuncs)
del allfuncs
__version__ = "4.1.0"

1
tiramisu/__version__.py Normal file
View file

@ -0,0 +1 @@
__version__ = "5.2.0a26"

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -26,6 +26,7 @@ from .error import (
LeadershipError,
ValueErrorWarning,
PropertiesOptionError,
AttributeOptionError,
)
from .i18n import _
from .setting import (
@ -52,6 +53,13 @@ from .autolib import Calculation
TIRAMISU_VERSION = 5
class Fake_SubConfig:
def __init__(self, config_bag, path, index):
self.config_bag = config_bag
self.path = path
self.index = index
class TiramisuHelp:
_tmpl_help = " {0}\t{1}"
@ -116,17 +124,35 @@ class TiramisuHelp:
class CommonTiramisu(TiramisuHelp):
_validate_properties = True
_allow_dynoption = False
def _set_subconfig(self) -> None:
if self._subconfig:
if id(self._subconfig.config_bag) != id(self._config_bag):
subconfig = self._subconfig
self._subconfig = subconfig.__class__(option=subconfig.option,
index=subconfig.index,
path=subconfig.path,
config_bag=self._config_bag,
parent=subconfig.parent,
identifiers=subconfig.identifiers,
true_path=subconfig.true_path,
properties=subconfig.properties,
validate_properties=False,
check_dynamic_without_identifiers=False,
)
else:
self._subconfig._length = None
if not self._subconfig:
try:
self._subconfig = self._config_bag.context.get_sub_config(
self._config_bag,
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
allow_dynoption=True,
)
except AssertionError as err:
raise ConfigError(str(err))
def option_type(typ):
@ -152,6 +178,11 @@ def option_type(typ):
kwargs["is_group"] = True
return func(self, options_bag, *args[1:], **kwargs)
self._set_subconfig()
if (
not isinstance(typ, list) or "allow_dynoption" not in typ
) and self._subconfig.is_dynamic_without_identifiers:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
option = self._subconfig.option
error_type = None
if "dynamic" in types:
@ -200,7 +231,9 @@ def option_type(typ):
"please specify index with a follower option ({0}.{1})"
).format(self.__class__.__name__, func.__name__)
raise ConfigError(msg)
if self._validate_properties and "dont_validate_property" not in types:
if "validate_properties" in types or (
self._validate_properties and "dont_validate_property" not in types
):
settings = self._config_bag.context.get_settings()
parent = self._subconfig.parent
if parent and parent.transitive_properties:
@ -236,11 +269,12 @@ class CommonTiramisuOption(CommonTiramisu):
path: str,
index: Optional[int],
config_bag: ConfigBag,
subconfig: Optional[SubConfig]=None,
) -> None:
self._path = path
self._index = index
self._config_bag = config_bag
self._subconfig = None
self._subconfig = subconfig
self._set_subconfig()
@ -251,10 +285,14 @@ class _TiramisuOptionWalk:
validate_properties: bool,
*,
uncalculated: bool = False,
with_index: bool = True,
):
options = []
for sub_subconfig in subconfig.get_children(
validate_properties, uncalculated=uncalculated
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
check_dynamic_without_identifiers=False,
):
options.append(
TiramisuOption(
@ -272,36 +310,71 @@ class _TiramisuOptionOptionDescription:
_validate_properties = False
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def get(self):
"""Get Tiramisu option"""
return self._subconfig.option
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def isoptiondescription(self):
"""Test if option is an optiondescription"""
return self._subconfig.option.impl_is_optiondescription()
@option_type(["optiondescription"])
@option_type(["optiondescription", "allow_dynoption"])
def isleadership(self):
"""Test if option is a leader or a follower"""
return self._subconfig.option.impl_is_leadership()
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def description(
self,
with_quote: bool = False,
uncalculated: bool = False,
):
"""Get option description"""
if not uncalculated:
return self._subconfig.option.impl_get_display_name(self._subconfig)
return self._subconfig.option.impl_get_display_name(
self._subconfig, with_quote=with_quote
)
return self._subconfig.option._get_information(
self._subconfig,
"doc",
None,
)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
@option_type(
[
"optiondescription",
"option",
"symlink",
"with_or_without_index",
"allow_dynoption",
]
)
def name(
self,
*,
@ -310,9 +383,19 @@ class _TiramisuOptionOptionDescription:
"""Get option name"""
if uncalculated:
return self._subconfig.option.impl_getname()
if self._subconfig.is_dynamic_without_identifiers:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
return self._subconfig.true_path.rsplit(".", 1)[-1]
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"])
@option_type(
[
"optiondescription",
"option",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def path(
self,
*,
@ -321,8 +404,19 @@ class _TiramisuOptionOptionDescription:
"""Get option path"""
if uncalculated:
return self._subconfig.option.impl_getpath()
if self._subconfig.is_dynamic_without_identifiers:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
return self._subconfig.true_path
def parent(self):
parent = self._subconfig.parent
return TiramisuOption(
parent.path,
None,
self._config_bag,
subconfig=parent,
)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def has_dependency(
self,
@ -332,34 +426,118 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_has_dependency(self_is_dep)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def dependencies(self):
def dependencies(
self,
*,
uncalculated: bool = False,
):
"""Get dependencies from this option"""
options = []
for option in self._subconfig.option.get_dependencies(self._config_bag.context):
context = self._config_bag.context
index = self._index
parent = self._subconfig.parent
parent_option = parent.option
for is_default, woption in self._subconfig.option.get_dependencies(
self._config_bag.context
):
option = woption()
if not uncalculated and option.issubdyn():
for subconfig in context.get_dynamic_from_dyn_option(
self._subconfig, option
):
if subconfig.properties is None:
subconfig.properties = undefined
if is_default and subconfig.config_bag.context.get_owner(subconfig) != owners.default:
continue
options.append(
TiramisuOption(
option().impl_getpath(),
subconfig.path,
None,
self._config_bag,
allow_dynoption=True,
)
)
elif not uncalculated and option.impl_is_dynoptiondescription():
for subconfig in context.get_dynamic_from_dyn_option(
self._subconfig, option
):
if is_default and subconfig.config_bag.context.get_owner(subconfig) != owners.default:
continue
options.append(
TiramisuOption(
subconfig.path,
None,
self._config_bag,
)
)
else:
if (
not option.impl_is_optiondescription()
and option.impl_is_follower()
and parent_option.impl_is_leadership()
and parent_option.in_same_leadership(option)
):
if index is not None:
current_indexes = [index]
else:
current_indexes = range(parent.get_length_leadership())
else:
current_indexes = [None]
for current_index in current_indexes:
t_option = TiramisuOption(
option.impl_getpath(),
current_index,
self._config_bag,
allow_dynoption=uncalculated,
)
t_option._set_subconfig()
subconfig = t_option._subconfig
if is_default and subconfig.config_bag.context.get_owner(subconfig) != owners.default:
continue
options.append(
t_option,
)
return options
@option_type(["option", "optiondescription", "symlink", "with_or_without_index"])
def type(self):
@option_type(
[
"option",
"optiondescription",
"symlink",
"with_or_without_index",
"allow_dynoption",
]
)
def type(self, only_self=False, translation=False):
"""Get de option type"""
option = self._subconfig.option
if option.impl_is_optiondescription():
return "optiondescription"
return option.get_type()
if translation:
type_ = _("optiondescription")
else:
type_ = "optiondescription"
elif only_self and option.impl_is_symlinkoption():
if translation:
type_ = _("symlink")
else:
type_ = "symlink"
else:
type_ = option.get_type(translation=translation)
return type_
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def extra(self, extra):
"""Get de option extra"""
return self._subconfig.option.impl_get_extra(extra)
@option_type(["option", "optiondescription", "symlink", "with_or_without_index"])
@option_type(
[
"option",
"optiondescription",
"symlink",
"with_or_without_index",
"allow_dynoption",
]
)
def isdynamic(self, *, only_self: bool = False):
"""Test if option is a dynamic optiondescription"""
if not only_self:
@ -369,7 +547,7 @@ class _TiramisuOptionOptionDescription:
and self._subconfig.option.impl_is_dynoptiondescription()
)
@option_type(["option", "leadership"])
@option_type(["option", "leadership", "allow_dynoption"])
def leader(self):
"""Get the leader option for a leadership or a follower option"""
option = self._subconfig.option
@ -381,6 +559,7 @@ class _TiramisuOptionOptionDescription:
leadership.option.get_leader(),
None,
False,
config_bag=self._config_bag,
)
return TiramisuOption(
leader_subconfig.path,
@ -403,6 +582,7 @@ class _TiramisuOptionOptionDescription:
follower,
None,
False,
config_bag=self._config_bag,
)
ret.append(
TiramisuOption(
@ -414,15 +594,31 @@ class _TiramisuOptionOptionDescription:
)
return ret
@option_type(["dynamic", "with_or_without_index"])
@option_type(["dynamic", "with_or_without_index", "allow_dynoption"])
def identifiers(
self,
only_self: bool = False,
uncalculated: bool = False,
convert: bool = False,
):
"""Get identifiers for dynamic option"""
subconfig = self._subconfig
if not only_self:
if self._subconfig.is_dynamic_without_identifiers and not uncalculated:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
if not convert:
return self._subconfig.identifiers
identifiers = []
dynconfig = None
while not dynconfig:
if subconfig.option.impl_is_optiondescription() and subconfig.option.impl_is_dynoptiondescription():
dynconfig = subconfig
subconfig = subconfig.parent
for identifier in self._subconfig.identifiers:
if identifier is None:
continue
identifiers.append(dynconfig.option.convert_identifier_to_path(identifier))
return identifiers
if (
not self._subconfig.option.impl_is_optiondescription()
or not self._subconfig.option.impl_is_dynoptiondescription()
@ -430,38 +626,40 @@ class _TiramisuOptionOptionDescription:
raise ConfigError(
_(
"the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True"
).format(self._subconfig.path)
).format(self._subconfig.path),
subconfig=subconfig,
)
return self._subconfig.option.get_identifiers(
self._subconfig.parent,
uncalculated=uncalculated,
convert=convert,
)
class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
"""Manage option"""
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def ismulti(self):
"""Test if option could have multi value"""
return self._subconfig.option.impl_is_multi()
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issubmulti(self):
"""Test if option could have submulti value"""
return self._subconfig.option.impl_is_submulti()
@option_type(["option", "with_or_without_index", "symlink"])
@option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isleader(self):
"""Test if option is a leader"""
return self._subconfig.option.impl_is_leader()
@option_type(["option", "with_or_without_index", "symlink"])
@option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isfollower(self):
"""Test if option is a follower"""
return self._subconfig.option.impl_is_follower()
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issymlinkoption(self) -> bool:
"""Test if option is a symlink option"""
return self._subconfig.option.impl_is_symlinkoption()
@ -483,11 +681,19 @@ class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
return r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
@option_type(["option", "with_or_without_index", "symlink"])
def index(self):
def index(self, index=undefined):
"""Get index of option"""
if index is undefined:
return self._subconfig.index
subconfig = self._subconfig.parent.get_child(
self._subconfig.option,
index,
True,
config_bag=self._config_bag,
)
return TiramisuOption(self._path, index, self._config_bag, subconfig=subconfig)
@option_type(["symlink", "optiondescription"])
@option_type(["symlink", "optiondescription", "allow_dynoption"])
def option(self, *args, **kwargs):
"""For OptionDescription get sub option, for symlinkoption get the linked option"""
if self._subconfig.option.impl_is_optiondescription():
@ -531,14 +737,33 @@ class TiramisuOptionOwner(CommonTiramisuOption):
_validate_properties = True
@option_type(["symlink", "option", "with_index"])
def get(self):
def get(self, only_self=False):
"""Get owner for a specified option"""
return self._config_bag.context.get_owner(self._subconfig)
return self._config_bag.context.get_owner(
self._subconfig, validate_meta=not only_self
)
@option_type(["symlink", "option", "with_index"])
def default(self):
values = self._config_bag.context.get_values()
return values.get_default_owner(self._subconfig)
@option_type(["symlink", "option", "with_index"])
def isdefault(self):
"""Is option has defaut value"""
return self._config_bag.context.get_owner(self._subconfig) == owners.default
subconfig = self._subconfig
s_properties = subconfig.properties
if (
"frozen" in s_properties
and "force_default_on_freeze" in s_properties
):
return True
context = self._config_bag.context
subconfig = context._get(
subconfig,
True,
)
return not context.get_values().hasvalue(subconfig.path, index=subconfig.index)
@option_type(["option", "with_index"])
def set(
@ -562,7 +787,9 @@ class TiramisuOptionProperty(CommonTiramisuOption):
_validate_properties = False
@option_type(["option", "optiondescription", "with_index", "symlink"])
@option_type(
["option", "optiondescription", "with_index", "symlink", "allow_dynoption"]
)
def get(
self,
*,
@ -571,6 +798,8 @@ class TiramisuOptionProperty(CommonTiramisuOption):
uncalculated: bool = False,
):
"""Get properties for an option"""
if self._subconfig.is_dynamic_without_identifiers and not uncalculated:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
settings = self._config_bag.context.get_settings()
if not only_raises:
return settings.getproperties(
@ -582,6 +811,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
self._subconfig,
uncalculated=uncalculated,
apply_requires=apply_requires,
not_unrestraint=True,
)
@option_type(["option", "optiondescription", "with_or_without_index"])
@ -690,9 +920,16 @@ class TiramisuOptionInformation(CommonTiramisuOption):
"""Manage option's informations"""
_validate_properties = False
_allow_dynoption = True
@option_type(["option", "optiondescription", "with_or_without_index", "symlink"])
@option_type(
[
"option",
"optiondescription",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def get(
self,
name: str,
@ -705,7 +942,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
default,
)
@option_type(["option", "optiondescription"])
@option_type(["option", "optiondescription", "allow_dynoption"])
def set(self, key: str, value: Any) -> None:
"""Set information"""
self._config_bag.context.get_values().set_information(
@ -714,7 +951,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
value,
)
@option_type(["option", "optiondescription"])
@option_type(["option", "optiondescription", "allow_dynoption"])
def remove(
self,
key: str,
@ -725,7 +962,15 @@ class TiramisuOptionInformation(CommonTiramisuOption):
path=self._path,
)
@option_type(["option", "optiondescription", "with_or_without_index", "symlink"])
@option_type(
[
"option",
"optiondescription",
"with_or_without_index",
"symlink",
"allow_dynoption",
]
)
def list(self) -> list:
"""List information's keys"""
lst1 = set(self._subconfig.option._list_information())
@ -801,14 +1046,11 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
option = self._subconfig.option
if (
not isinstance(value, Calculation)
and isinstance(value, list)
and option.impl_is_leader()
and len(value) < self._subconfig.parent.get_length_leadership()
):
raise LeadershipError(
_("cannot reduce length of the leader {}" "").format(
option.impl_get_display_name(self._subconfig, with_quote=True)
)
)
raise LeadershipError(self._subconfig, "leadership-reduce")
values = self._config_bag.context.get_values()
return values.set_value(self._subconfig, value)
@ -841,15 +1083,10 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
if uncalculated:
return self._subconfig.option.impl_getdefault()
if self._subconfig.option.impl_is_follower() and self._subconfig.index is None:
msg = _("please specify index with a follower option ({0}.{1})").format(
self.__class__.__name__, func.__name__
msg = _("please specify index with a follower option ({0})").format(
self._subconfig.path
)
raise ConfigError(msg)
if (
"force_store_value" in self._subconfig.properties
and "force_store_value" in self._config_bag.properties
):
return self._get(self._subconfig)
return self._config_bag.context.get_values().get_default_value(self._subconfig)
@option_type(
@ -875,13 +1112,15 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
return False
return True
@option_type(["choice", "with_index"])
@option_type(["choice", "with_index", "allow_dynoption"])
def list(
self,
*,
uncalculated: bool = False,
):
"""All values available for a ChoiceOption"""
if self._subconfig.is_dynamic_without_identifiers and not uncalculated:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
return self._subconfig.option.impl_get_values(
self._subconfig,
uncalculated,
@ -906,14 +1145,15 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
def mandatory(self):
"""Return path of options with mandatory property without any value"""
subconfig = self._subconfig
if subconfig.option.impl_is_optiondescription():
ori_config_bag = self._subconfig.config_bag
config_bag = ori_config_bag.copy()
config_bag.properties -= {"mandatory", "empty", "warnings"}
config_bag.set_permissive()
self._subconfig.config_bag = config_bag
try:
if subconfig.option.impl_is_optiondescription():
options = []
for subconfig in self._config_bag.context.walk(
for subconfig in config_bag.context.walk(
self._subconfig,
only_mandatory=True,
):
@ -933,7 +1173,11 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
)
except PropertiesOptionError as err:
return err.proptype == ["mandatory"] or err.proptype == ["empty"]
self._subconfig.config_bag = ori_config_bag
return False
except Exception as err:
self._subconfig.config_bag = ori_config_bag
raise err from err
def _registers(
@ -970,6 +1214,18 @@ class TiramisuConfig(TiramisuHelp, _TiramisuOptionWalk):
if isinstance(config, KernelGroupConfig):
return GroupConfig(config)
def type(self):
"""get the type"""
config = self._config_bag.context
if isinstance(config, KernelConfig):
return "config"
if isinstance(config, KernelMetaConfig):
return "metaconfig"
if isinstance(config, KernelMixConfig):
return "mixconfig"
if isinstance(config, KernelGroupConfig):
return "groupconfig"
def name(self):
"""get the name"""
return self._config_bag.context.impl_getname()
@ -991,13 +1247,12 @@ class TiramisuOption(
index: Optional[int] = None,
config_bag: Optional[ConfigBag] = None,
*,
subconfig: Optional[SubConfig] = None,
allow_dynoption: bool = False,
subconfig: Optional[SubConfig] = None,
) -> None:
self._path = path
self._index = index
self._config_bag = config_bag
self._allow_dynoption = allow_dynoption
self._subconfig = subconfig
if not self._registers:
_registers(self._registers, "TiramisuOption")
@ -1015,7 +1270,24 @@ class TiramisuOption(
self._path,
self._index,
self._config_bag,
self._subconfig,
)
if subfunc in ["forcepermissive", "unrestraint", "nowarnings"]:
if subfunc == "unrestraint" and self._config_bag.is_unrestraint:
return self
#if self._orig_config_bags:
# msg = _(
# "do not use unrestraint, nowarnings or forcepermissive together"
# )
# raise ConfigError(msg)
config_bag = self._config_bag.copy()
if subfunc == "unrestraint":
config_bag.unrestraint()
elif subfunc == "nowarnings":
config_bag.nowarnings()
else:
config_bag.set_permissive()
return self.__class__(self._path, self._index, config_bag)
raise ConfigError(
_("please specify a valid sub function ({0}.{1}) for {2}").format(
self.__class__.__name__, subfunc, self._path
@ -1033,25 +1305,29 @@ class TiramisuOption(
subconfig=sub_subconfig,
)
@option_type("optiondescription")
@option_type(["optiondescription", "allow_dynoption"])
def group_type(self):
"""Get type for an optiondescription (only for optiondescription)"""
self._set_subconfig()
return self._subconfig.option.impl_get_group_type()
@option_type("optiondescription")
@option_type(["optiondescription", "validate_properties", "allow_dynoption"])
def list(
self,
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options inside an option description (by default list only option)"""
self._set_subconfig()
if self._subconfig.is_dynamic_without_identifiers and not uncalculated:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
return self._list(
self._subconfig,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(
@ -1089,6 +1365,10 @@ class TiramisuOption(
if self._tiramisu_dict is None: # pragma: no cover
self._load_dict()
return self._tiramisu_dict.set_updates(body)
#
# def forcepermissive(self):
# self._set_subconfig()
# self._subconfig.config_bag.set_permissive()
class TiramisuContextInformation(TiramisuConfig):
@ -1157,6 +1437,20 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
root,
only_mandatory=True,
):
if id(subconfig.config_bag) != id(config_bag):
subconfig = subconfig.__class__(option=subconfig.option,
index=subconfig.index,
path=subconfig.path,
config_bag=config_bag,
parent=subconfig.parent,
identifiers=subconfig.identifiers,
true_path=subconfig.true_path,
properties=subconfig.properties,
validate_properties=False,
check_dynamic_without_identifiers=False,
)
else:
subconfig._length = None
options.append(
TiramisuOption(
subconfig.path,
@ -1172,6 +1466,8 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
self,
path: str,
value: Any,
*,
index: Optional[int] = None,
only_config=undefined,
force_default=undefined,
force_default_if_same=undefined,
@ -1187,14 +1483,22 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
kwargs["force_default_if_same"] = force_default_if_same
if force_dont_change_value is not undefined:
kwargs["force_dont_change_value"] = force_dont_change_value
option_bag = OptionBag(
None,
None,
context = self._config_bag.context
if isinstance(context, KernelGroupConfig):
subconfig = Fake_SubConfig(
self._config_bag,
path=path,
path,
index,
)
else:
subconfig = context.get_sub_config(
self._config_bag,
path,
index,
validate_properties=False,
)
return self._config_bag.context.set_value(
option_bag,
subconfig,
value,
**kwargs,
)
@ -1218,6 +1522,7 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
with_default_owner: bool = False,
):
"""Export all values"""
self._force_store_value()
exportation = deepcopy(self._config_bag.context.get_values()._values)
if not with_default_owner:
del exportation[None]
@ -1233,6 +1538,10 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
if None not in values:
cvalues._values[None] = {None: [None, current_owner]}
def _force_store_value(self):
descr = self._config_bag.context.get_description()
descr.impl_build_force_store_values(self._config_bag)
class TiramisuContextOwner(TiramisuConfig):
"""Global owner"""
@ -1527,7 +1836,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
def get(self):
"""Get Tiramisu option"""
return None
return self._config_bag.context.get_description()
def isleadership(self):
"""Test if option is a leader or a follower"""
@ -1548,15 +1857,11 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
None,
)
def name(self):
"""Get option name"""
return None
def path(
self,
):
"""Get option path"""
return None
return self._config_bag.context.get_config_path()
def has_dependency(
self,
@ -1573,15 +1878,12 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
"""Test if option is a dynamic optiondescription"""
return False
def type(self):
"""Get de option type"""
return "optiondescription"
def list(
self,
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options (by default list only option)"""
root = self._config_bag.context.get_root(self._config_bag)
@ -1589,6 +1891,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
root,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(self, clearable="all", remotable="minimum"):
@ -1619,6 +1922,9 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
self._load_dict()
return self._tiramisu_dict.set_updates(body)
def __repr__(self):
return f'<{self.__class__.__name__} path="{self.path()}">'
class _TiramisuContextConfigReset:
def reset(self):
@ -1808,6 +2114,8 @@ class TiramisuAPI(TiramisuHelp):
def __getattr__(self, subfunc: str) -> Any:
if subfunc in ["forcepermissive", "unrestraint", "nowarnings"]:
if subfunc == "unrestraint" and self._config_bag.is_unrestraint:
return self
if self._orig_config_bags:
msg = _(
"do not use unrestraint, nowarnings or forcepermissive together"
@ -1852,9 +2160,7 @@ class TiramisuAPI(TiramisuHelp):
class ConfigProp(TiramisuAPI, TiramisuContextOption):
def __repr__(self):
return f"<Config path=None>"
pass
class Config(TiramisuAPI, TiramisuContextOption):
"""Root config object that enables us to handle the configuration options"""
@ -1891,11 +2197,8 @@ class Config(TiramisuAPI, TiramisuContextOption):
except ConfigError:
pass
def __repr__(self):
return f"<Config path=None>"
class MetaConfig(TiramisuAPI):
class MetaConfig(TiramisuAPI, TiramisuContextOption):
"""MetaConfig object that enables us to handle the sub configuration's options
with common root optiondescription
"""
@ -1963,7 +2266,7 @@ class MixConfig(TiramisuAPI):
display_name=display_name,
)
settings = config.get_settings()
properties = settings.get_context_properties(config.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
config_bag = ConfigBag(
config,

View file

@ -1,4 +1,4 @@
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -22,7 +22,15 @@ from typing import Any, Optional, Union, Callable, Dict, List
from itertools import chain
import weakref
from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning
from .error import (
PropertiesOptionError,
ConfigError,
LeadershipError,
ValueWarning,
CancelParam,
display_list,
errors,
)
from .i18n import _
from .setting import undefined, ConfigBag
from .function import FUNCTION_WAITING_FOR_DICT, FUNCTION_WAITING_FOR_ERROR
@ -49,6 +57,7 @@ def get_calculated_value(
has_calculation = True
elif isinstance(value, list):
# if value is a list, do subcalculation
value = value.copy()
for idx, val in enumerate(value):
value[idx], _has_calculation = get_calculated_value(
subconfig,
@ -148,26 +157,32 @@ class ParamDynOption(ParamOption):
)
if not isinstance(identifiers, list):
raise Exception(
f"identifiers in ParamDynOption must be a list, not {identifiers}"
_("identifiers in ParamDynOption must be a list, not {0}").format(
identifiers
)
)
if not isinstance(optional, bool):
raise Exception(
f"optional in ParamDynOption must be a boolean, not {optional}"
_("optional in ParamDynOption must be a boolean, not {0}").format(
optional
)
)
self.identifiers = identifiers
self.optional = optional
class ParamSelfOption(Param):
__slots__ = "whole"
__slots__ = ("whole", "dynamic")
def __init__(
self,
whole: bool = undefined,
dynamic: bool = True,
) -> None:
"""whole: send all value for a multi, not only indexed value"""
if whole is not undefined:
self.whole = whole
self.dynamic = dynamic
class ParamValue(Param):
@ -203,9 +218,11 @@ class ParamInformation(Param):
def set_option(self, option: "Option" = None) -> None:
if not hasattr(self, "self_option"):
raise ConfigError("cannot add option in information after creating config")
raise ConfigError(
_("cannot add option in information after creating config")
)
if self.option:
raise ConfigError("cannot redefine option in information")
raise ConfigError(_("cannot redefine option in information"))
if not option.impl_is_optiondescription():
if option.impl_is_symlinkoption():
raise ValueError(
@ -445,14 +462,7 @@ def manager_callback(
or param.raisepropertyerror
):
raise err from err
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
)
raise ConfigError(
_("unable to carry out a calculation for {}, {}").format(
display_name, err
)
) from err
raise ConfigError(err, subconfig=subconfig)
except ValueError as err:
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
@ -464,20 +474,15 @@ def manager_callback(
) from err
except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror
# cannot access, simulate a propertyerror
raise PropertiesOptionError(
subconfig,
["configerror"],
config_bag.context.get_settings(),
)
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
errors.raise_carry_out_calculation_error(
subconfig, _("unable to get value for calculating {0}, {1}"), err
)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
)
) from err
return value
def get_option_bag(
@ -504,17 +509,18 @@ def manager_callback(
index_,
validate_properties=not self_calc,
properties=properties,
valid_conflict=False,
)
except PropertiesOptionError as err:
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if param.notraisepropertyerror or param.raisepropertyerror:
raise err from err
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to carry out a calculation for {}, {}").format(
display_name, err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to carry out a calculation for {0}, {1}"),
err,
option=option,
)
) from err
except ValueError as err:
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ValueError(
@ -524,18 +530,18 @@ def manager_callback(
) from err
except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror
# cannot access, simulate a propertyerror
raise PropertiesOptionError(
param,
["configerror"],
config_bag.context.get_settings(),
)
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to get value for calculating {0}, {1}"),
err,
option=option,
)
) from err
return subsubconfig
if isinstance(param, ParamValue):
@ -552,14 +558,15 @@ def manager_callback(
true_path=subconfig.path,
)
if isinstance(isubconfig, list):
display_name = option.impl_get_display_name(
subconfig, with_quote=True
)
search_name = search_option.impl_get_display_name(
None, with_quote=True
)
raise ConfigError(
f"cannot find information for {display_name}, {search_name} is a dynamic option"
errors.raise_carry_out_calculation_error(
subconfig,
_("cannot find information for {0}, {1} is a dynamic option"),
None,
option=option,
extra_keys=[search_name],
)
else:
isubconfig = get_option_bag(
@ -579,12 +586,12 @@ def manager_callback(
param.default_value,
)
except ValueError as err:
display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(
_("unable to get value for calculating {0}, {1}").format(
display_name, err
errors.raise_carry_out_calculation_error(
subconfig,
_("unable to get value for calculating {0}, {1}"),
err,
option=option,
)
) from err
if isinstance(param, ParamIndex):
return index
@ -594,17 +601,47 @@ def manager_callback(
not option.impl_is_optiondescription()
or not option.impl_is_dynoptiondescription()
):
display_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
)
raise ConfigError(
errors.raise_carry_out_calculation_error(
subconfig,
_(
"option {0} is not a dynoptiondescription or in a dynoptiondescription"
).format(display_name)
),
None,
option=option,
)
if subconfig.identifiers is None:
# if uncalculated
return
return subconfig.identifiers[param.identifier_index]
if isinstance(param, ParamSelfOption):
search_option = subconfig.option
if subconfig.option.issubdyn() and not param.dynamic:
subconfigs = subconfig.parent.parent.get_common_child(
search_option,
true_path=subconfig.path,
validate_properties=False,
)
values = []
properties = config_bag.context.get_settings().getproperties(
subconfig,
uncalculated=True,
) - {'validator', 'mandatory', 'empty'}
for subconfig_ in subconfigs:
if subconfig.path == subconfig_.path:
values.append(orig_value)
else:
subconfig_.properties = properties
values.append(get_value(
config_bag,
subconfig_,
param,
True,
))
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return values
return {"name": search_option.impl_get_display_name(subconfig), "value": values}
else:
value = calc_self(
param,
index,
@ -673,7 +710,37 @@ def manager_callback(
parent,
)
except AttributeError as err:
raise ConfigError(err) from err
if parent.path:
child_path = parent.path + "." + name
else:
child_path = name
if param.optional:
raise CancelParam(
callbk_option.impl_getpath(), child_path
)
identifiers = doption.get_identifiers(parent)
if not identifiers:
errors.raise_carry_out_calculation_error(
subconfig,
_(
'cannot calculate arguments for {0}, {1} with identifier "{2}", there is no identifiers'
),
err,
extra_keys=[identifier],
)
else:
identifiers_list = display_list(
identifiers, add_quote=True
)
errors.raise_carry_out_calculation_error(
subconfig,
_(
'cannot calculate arguments for {0}, {1} with identifier "{2}", list of valid identifiers: {3}'
),
err,
extra_keys=[identifier, identifiers_list],
)
new_parents.append(
parent.get_child(
doption,
@ -724,7 +791,9 @@ def manager_callback(
]
values = None
for subconfig in subconfigs:
callbk_option = subconfig.option
if isinstance(subconfig, PropertiesOptionError):
value = subconfig
else:
value = get_value(
config_bag,
subconfig,
@ -739,6 +808,8 @@ def manager_callback(
value = values
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return value
# FIXME the last one?
callbk_option = subconfig.option
return {"name": callbk_option.impl_get_display_name(subconfig), "value": value}
@ -777,8 +848,11 @@ def carry_out_calculation(
and option.impl_is_follower()
and index is None
):
raise ConfigError(
f"the follower {option.impl_get_display_name(subconfig, with_quote=True)} must have index in carry_out_calculation!"
errors.raise_carry_out_calculation_error(
subconfig,
_("the follower {0} must have index in carry_out_calculation!"),
None,
option=option,
)
def fake_items(iterator):
@ -788,6 +862,17 @@ def carry_out_calculation(
kwargs = {}
config_bag = config_bag.copy()
config_bag.set_permissive()
subconfig = subconfig.__class__(option=subconfig.option,
index=subconfig.index,
path=subconfig.path,
config_bag=config_bag,
parent=subconfig.parent,
identifiers=subconfig.identifiers,
true_path=subconfig.true_path,
properties=subconfig.properties,
validate_properties=False,
check_dynamic_without_identifiers=False,
)
if callback_params:
for key, param in chain(
fake_items(callback_params.args), callback_params.kwargs.items()
@ -828,6 +913,12 @@ def carry_out_calculation(
args.append(err)
else:
kwargs[key] = err
except CancelParam as err:
if callback.__name__ in FUNCTION_WAITING_FOR_ERROR:
if key is None:
args.append(err)
else:
kwargs[key] = err
ret = calculate(
subconfig,
callback,
@ -843,32 +934,13 @@ def carry_out_calculation(
and option.impl_is_follower()
and not option.impl_is_submulti()
):
if args or kwargs:
raise LeadershipError(
_(
'the "{}" function with positional arguments "{}" '
'and keyword arguments "{}" must not return '
'a list ("{}") for the follower option {}'
""
).format(
callback.__name__,
args,
kwargs,
ret,
option.impl_get_display_name(subconfig, with_quote=True),
)
)
else:
raise LeadershipError(
_(
'the "{}" function must not return a list ("{}") '
"for the follower option {}"
""
).format(
callback.__name__,
ret,
option.impl_get_display_name(subconfig, with_quote=True),
)
subconfig,
"leadership-follower-callback-list",
callback=callback.__name__,
args=args,
kwargs=kwargs,
ret=ret,
)
return ret
@ -893,28 +965,27 @@ def calculate(
except (ValueError, ValueWarning) as err:
if allow_value_error:
if force_value_warning:
raise ValueWarning(str(err))
raise ValueWarning(msg=str(err))
raise err from err
error = err
except ConfigError as err:
err.subconfig = subconfig
raise err from err
except Exception as err:
error = err
if args or kwargs:
msg = _(
'unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" '
"for option {2}"
).format(
str(error),
'unexpected error "{1}" in function "{2}" with arguments "{3}" and "{4}" '
"for option {0}"
)
extra_keys = [
callback.__name__,
subconfig.option.impl_get_display_name(subconfig, with_quote=True),
args,
kwargs,
)
]
else:
msg = _('unexpected error "{0}" in function "{1}" for option {2}' "").format(
str(error),
callback.__name__,
subconfig.option.impl_get_display_name(subconfig, with_quote=True),
msg = _('unexpected error "{1}" in function "{2}" for option {0}')
extra_keys = [callback.__name__]
errors.raise_carry_out_calculation_error(
subconfig, msg, error, extra_keys=extra_keys
)
raise ConfigError(msg) from error

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"cache used by storage"
# Copyright (C) 2013-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2013-2026 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

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -25,7 +25,13 @@ from copy import copy, deepcopy
from typing import Optional, List, Any, Union
from os.path import commonprefix
from .error import PropertiesOptionError, ConfigError, ConflictError, LeadershipError
from .error import (
PropertiesOptionError,
ConfigError,
ConflictError,
LeadershipError,
AttributeOptionError,
)
from .option import DynOptionDescription, Leadership, Option
from .setting import ConfigBag, Settings, undefined, groups
from .value import Values, owners
@ -36,12 +42,18 @@ from . import autolib
def get_common_path(path1, path2):
if None in (path1, path2):
return None
common_path = commonprefix([path1, path2])
if common_path in [path1, path2]:
all_paths = [path1, path2]
if common_path in all_paths:
# od.st is not the common_path of od.st_in
all_paths.remove(common_path)
if all_paths[0].startswith(common_path + '.'):
return common_path
if common_path.endswith("."):
return common_path[:-1]
if "." in common_path:
elif "." in common_path:
return common_path.rsplit(".", 1)[0]
return None
@ -68,6 +80,7 @@ class CCache:
self.reset_one_option_cache(
subconfig,
resetted_opts,
False,
)
subconfig.config_bag.properties = subconfig.config_bag.properties | {
"cache"
@ -80,26 +93,38 @@ class CCache:
self,
subconfig,
resetted_opts,
is_default,
*,
force=False,
):
"""reset cache for one option"""
if subconfig.path in resetted_opts:
if not force and subconfig.path in resetted_opts:
return
resetted_opts.append(subconfig.path)
config_bag = subconfig.config_bag
for woption in subconfig.option.get_dependencies(subconfig.option):
if not force:
# if is_default and config_bag.context.get_owner(subconfig) != owners.default:
# return
for is_default, woption in subconfig.option.get_dependencies(subconfig.option):
option = woption()
if option.issubdyn():
# it's an option in dynoptiondescription, remove cache for all generated option
if option.impl_getpath() == subconfig.option.impl_getpath():
force = True
subconfig = subconfig.parent.parent
self.reset_cache_dyn_option(
config_bag,
subconfig,
option,
resetted_opts,
is_default,
force,
)
elif option.impl_is_dynoptiondescription():
self._reset_cache_dyn_optiondescription(
self.reset_cache_dyn_optiondescription(
option,
config_bag,
resetted_opts,
is_default,
)
else:
option_subconfig = self.get_sub_config(
@ -112,6 +137,7 @@ class CCache:
self.reset_one_option_cache(
option_subconfig,
resetted_opts,
is_default,
)
del option
subconfig.option.reset_cache(
@ -120,13 +146,7 @@ class CCache:
resetted_opts,
)
def _reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
):
# reset cache for all chidren
def get_dynamic_from_dyn_optiondescription(self, config_bag, option):
path = option.impl_getpath()
if "." in path:
parent_path = path.rsplit(".", 1)[0]
@ -139,13 +159,27 @@ class CCache:
)
else:
parent_subconfig = self.get_root(config_bag)
for subconfig in parent_subconfig.dyn_to_subconfig(
return parent_subconfig.dyn_to_subconfig(
option,
False,
)
def reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
is_default,
):
# reset cache for all chidren
for subconfig in self.get_dynamic_from_dyn_optiondescription(
config_bag,
option,
):
self.reset_one_option_cache(
subconfig,
resetted_opts,
is_default,
)
for walk_subconfig in self.walk(
subconfig,
@ -155,17 +189,27 @@ class CCache:
self.reset_one_option_cache(
walk_subconfig,
resetted_opts,
is_default,
)
def reset_cache_dyn_option(
self,
config_bag,
option,
resetted_opts,
):
currents = [self.get_root(config_bag)]
def get_dynamic_from_dyn_option(self, subconfig, option):
config_bag = subconfig.config_bag
sub_paths = option.impl_getpath()
for sub_path in sub_paths.split("."):
if not subconfig.path:
current_paths = []
current_paths_max_index = 0
else:
current_paths = subconfig.path.split(".")
current_paths_max_index = len(current_paths) - 1
current_subconfigs = []
parent = subconfig
while True:
current_subconfigs.insert(0, parent)
parent = parent.parent
if not parent or parent.path is None:
break
currents = [self.get_root(config_bag)]
for idx, sub_path in enumerate(sub_paths.split(".")):
new_currents = []
for current in currents:
sub_option = current.option.get_child(
@ -175,6 +219,12 @@ class CCache:
allow_dynoption=True,
)
if sub_option.impl_is_dynoptiondescription():
if (
idx <= current_paths_max_index
and sub_option == current_subconfigs[idx].option
):
new_currents.append(current_subconfigs[idx])
else:
new_currents.extend(
list(
current.dyn_to_subconfig(
@ -183,7 +233,6 @@ class CCache:
)
)
)
else:
new_currents.append(
current.get_child(
@ -194,10 +243,22 @@ class CCache:
),
)
currents = new_currents
for dyn_option_subconfig in currents:
return currents
def reset_cache_dyn_option(
self,
subconfig,
option,
resetted_opts,
is_default,
force,
):
for dyn_option_subconfig in self.get_dynamic_from_dyn_option(subconfig, option):
self.reset_one_option_cache(
dyn_option_subconfig,
resetted_opts,
is_default,
force=force,
)
@ -213,6 +274,7 @@ class SubConfig:
"apply_requires",
"transitive_properties",
"is_dynamic",
"is_dynamic_without_identifiers",
"identifiers",
"_length",
)
@ -227,8 +289,10 @@ class SubConfig:
identifiers: Optional[list[str]],
*,
true_path: Optional[str] = None,
properties: Union[list[str], undefined] = undefined,
# for python 3.9 properties: Union[list[str], undefined] = undefined,
properties=undefined,
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
) -> None:
self.index = index
self.identifiers = identifiers
@ -244,10 +308,26 @@ class SubConfig:
)
self.apply_requires = not is_follower or index is not None
self.true_path = true_path
if parent and parent.is_dynamic or self.option.impl_is_dynoptiondescription():
if self.option.impl_is_dynoptiondescription():
self.is_dynamic = True
self.is_dynamic_without_identifiers = identifiers is None or (
parent and identifiers == parent.identifiers
)
if (
check_dynamic_without_identifiers
and parent
and parent.is_dynamic
and parent.is_dynamic_without_identifiers
and self.is_dynamic_without_identifiers
!= parent.is_dynamic_without_identifiers
):
raise AttributeOptionError(true_path, "option-dynamic")
elif parent:
self.is_dynamic = parent.is_dynamic
self.is_dynamic_without_identifiers = parent.is_dynamic_without_identifiers
else:
self.is_dynamic = False
self.is_dynamic_without_identifiers = False
self._properties = properties
if validate_properties:
if self.path and self._properties is undefined:
@ -347,26 +427,55 @@ class SubConfig:
validate_properties,
*,
uncalculated: bool = False,
with_index: bool = True,
check_dynamic_without_identifiers: bool = True,
):
if self.option.impl_is_leadership() and not uncalculated:
if self.option.impl_is_leadership() and not uncalculated and with_index:
yield from self.get_leadership_children(validate_properties)
else:
children_name = []
for child in self.option.get_children():
if child.impl_is_dynoptiondescription() and not uncalculated:
yield from self.dyn_to_subconfig(
for dyn_child in self.dyn_to_subconfig(
child,
validate_properties,
):
yield dyn_child
if child.could_conflict:
name = dyn_child.path
if name in children_name:
raise ConflictError(
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
else:
try:
yield self.get_child(
child,
None,
validate_properties,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
)
except PropertiesOptionError as err:
if err.proptype in (["mandatory"], ["empty"]):
raise err
if child.could_conflict:
name = child.impl_getpath()
if name in children_name:
raise ConflictError(
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
def get_child(
self,
@ -375,12 +484,12 @@ class SubConfig:
validate_properties: bool,
*,
properties=undefined,
allow_dynoption: bool = False,
identifier: Optional[str] = None,
name: Optional[str] = None,
check_index: bool = True,
config_bag: ConfigBag = None,
true_path: Optional[str] = None,
check_dynamic_without_identifiers: bool = True,
) -> "SubConfig":
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if config_bag is None:
@ -403,26 +512,21 @@ class SubConfig:
option,
index,
path,
self.config_bag,
config_bag,
self,
identifiers,
properties=properties,
validate_properties=validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
)
if check_index and index is not None:
if option.impl_is_optiondescription() or not option.impl_is_follower():
raise ConfigError("index must be set only with a follower option")
raise ConfigError("index must be set only with a follower option", subconfig=subsubconfig,)
length = self.get_length_leadership()
if index >= length:
raise LeadershipError(
_(
'index "{0}" is greater than the leadership length "{1}" for option {2}'
).format(
index,
length,
option.impl_get_display_name(subsubconfig, with_quote=True),
)
subsubconfig, "leadership-greater", index=index, length=length
)
return subsubconfig
@ -458,14 +562,17 @@ class SubConfig:
self.identifiers,
validate_properties=False,
)
self._length = len(cconfig_bag.context.get_value(subconfig))
return self._length
#FIXME
#self._length = len(cconfig_bag.context.get_value(subconfig))
length = len(cconfig_bag.context.get_value(subconfig))
return length
def get_common_child(
self,
search_option: "BaseOption",
true_path: Optional[str] = None,
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
):
current_option_path = self.option.impl_getpath()
search_option_path = search_option.impl_getpath()
@ -483,10 +590,17 @@ class SubConfig:
parents = [self.parent]
else:
if common_path:
parent = self.parent
common_parent_number = common_path.count(".") + 1
for idx in range(current_option_path.count(".") - common_parent_number):
parent_count = current_option_path.count(".") - common_parent_number
if parent_count >= 0:
parent = self.parent
for idx in range(parent_count):
parent = parent.parent
elif parent_count == 0:
parent = self.parent
else:
# so -1
parent = self
parents = [parent]
else:
common_parent_number = 0
@ -525,22 +639,42 @@ class SubConfig:
None,
validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
config_bag=config_bag,
)
)
parents = new_parents
subconfigs = []
for parent in parents:
subconfigs.append(
parent.get_child(
try:
ret = parent.get_child(
search_option,
index,
validate_properties,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
config_bag=config_bag,
)
)
except PropertiesOptionError as err:
ret = err
subconfigs.append(ret)
if subconfigs_is_a_list:
return subconfigs
return subconfigs[0]
def change_context(self, context) -> "SubConfig":
config_bag = self.config_bag.copy()
config_bag.context = context
return SubConfig(
self.option,
self.index,
self.path,
config_bag,
self.parent,
self.identifiers,
true_path=self.true_path,
validate_properties=False,
)
class _Config(CCache):
"""Sub configuration management entry.
@ -593,80 +727,8 @@ class _Config(CCache):
"""get cache for values"""
return self._impl_values_cache # pylint: disable=no-member
# =============================================================================
# WALK
def find(
self,
option_bag,
bytype,
byname,
byvalue,
raise_if_not_found=True,
only_path=undefined,
only_option=undefined,
with_option=False,
):
"""
convenience method for finding an option that lives only in the subtree
:param first: return only one option if True, a list otherwise
:return: find list or an exception if nothing has been found
"""
# pylint: disable=too-many-arguments,too-many-locals
def _filter_by_value(soption_bag):
value = self.get_value(soption_bag)
if isinstance(value, list):
return byvalue in value
return value == byvalue
found = False
if only_path is not undefined:
def _fake_iter():
yield only_option
options = _fake_iter()
else:
options = option_bag.option.get_children_recursively(
bytype,
byname,
option_bag.config_bag,
)
for option in options:
path = option.impl_getpath()
soption_bag = OptionBag(
option,
None,
option_bag.config_bag,
)
if byvalue is not undefined and not _filter_by_value(soption_bag):
continue
if option_bag.config_bag.properties:
# remove option with propertyerror, ...
try:
self.get_sub_config(
option_bag.config_bag, # pylint: disable=no-member
path,
None,
validate_properties=True,
)
except PropertiesOptionError:
continue
found = True
if not with_option:
yield path
else:
yield path, option
self._find_return_results(
found,
raise_if_not_found,
)
def _find_return_results(self, found, raise_if_not_found):
if not found and raise_if_not_found:
raise AttributeError(_("no option found in config" " with these criteria"))
# # =============================================================================
# # WALK
def walk_valid_value(
self,
subconfig,
@ -711,6 +773,7 @@ class _Config(CCache):
properties=undefined,
true_path: Optional[str] = None,
allow_dynoption: bool = False,
valid_conflict: bool = True,
):
subconfig = self.get_root(config_bag)
if path is None:
@ -743,6 +806,32 @@ class _Config(CCache):
identifier, option = option
else:
identifier = None
if valid_conflict and option.could_conflict:
for ref in option.could_conflict:
child = ref()
if child.impl_is_dynoptiondescription():
for dyn_child in subconfig.dyn_to_subconfig(
child,
validate_properties,
):
if path == dyn_child.path:
raise ConflictError(
_('option name "{0}" is not unique in {1}').format(
name,
option.impl_get_display_name(
subconfig, with_quote=True
),
)
)
elif child.impl_getname() == name:
raise ConflictError(
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
subconfig = subconfig.get_child(
option,
index_,
@ -821,6 +910,15 @@ class _Config(CCache):
# =============================================================================
# Manage value
def set_value(
self,
subconfig,
value: Any,
) -> Any:
"""set value"""
self.get_settings().validate_properties(subconfig)
return self.get_values().set_value(subconfig, value)
def get_value(
self,
subconfig,
@ -868,9 +966,10 @@ class _Config(CCache):
subconfig, with_quote=True
)
raise LeadershipError(
_(
"the follower option {0} has greater length ({1}) than the leader length ({2})"
).format(option_name, follower_len, length)
subconfig,
"leadership-follower-greater",
index=follower_len,
length=length,
)
self.get_settings().validate_mandatory(
subconfig,
@ -939,6 +1038,8 @@ class _Config(CCache):
def get_owner(
self,
subconfig: "SubConfig",
*,
validate_meta=True,
):
"""get owner"""
subconfigs = self._get(
@ -949,13 +1050,14 @@ class _Config(CCache):
for sc in subconfigs:
owner = self.get_owner(
sc,
validate_meta=validate_meta,
)
if owner != owners.default:
break
else:
owner = owners.default
else:
owner = self.get_values().getowner(subconfigs)
owner = self.get_values().getowner(subconfigs, validate_meta=validate_meta)
return owner
@ -1236,6 +1338,10 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=super-init-not-called
names = []
for child in children:
if not isinstance(child, (KernelConfig, KernelGroupConfig)):
raise TypeError(
_("child must be a Config, GroupConfig, MixConfig or MetaConfig")
)
name_ = child._impl_name
names.append(name_)
if len(names) != len(set(names)):
@ -1263,61 +1369,58 @@ class KernelGroupConfig(_CommonConfig):
def reset_cache(
self,
option_bag,
subconfig,
resetted_opts=None,
):
if resetted_opts is None:
resetted_opts = []
if isinstance(self, KernelMixConfig):
super().reset_cache(
option_bag,
subconfig,
resetted_opts=copy(resetted_opts),
)
for child in self._impl_children:
if option_bag is not None:
coption_bag = option_bag.copy()
cconfig_bag = coption_bag.config_bag.copy()
cconfig_bag.context = child
coption_bag.config_bag = cconfig_bag
if subconfig is not None:
parent_subconfig = subconfig.change_context(child)
else:
coption_bag = None
parent_subconfig = None
child.reset_cache(
coption_bag,
parent_subconfig,
resetted_opts=copy(resetted_opts),
)
def set_value(
self,
option_bag,
subconfig,
value,
only_config=False,
):
"""Setattr not in current KernelGroupConfig, but in each children"""
ret = []
for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy()
cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child
if isinstance(child, KernelGroupConfig):
ret.extend(
child.set_value(
option_bag,
subconfig,
value,
only_config=only_config,
)
)
else:
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
try:
# GROUP
coption_bag = child.get_sub_option_bag(
coption_bag = child.get_sub_config(
cconfig_bag,
option_bag.path,
option_bag.index,
False,
subconfig.path,
subconfig.index,
validate_properties=False,
)
child.set_value(
coption_bag,
@ -1327,7 +1430,7 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=protected-access
ret.append(
PropertiesOptionError(
err._option_bag,
err.subconfig,
err.proptype,
err._settings,
err._opt_type,
@ -1390,7 +1493,7 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag.context = child
if cconfig_bag.properties is None:
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
@ -1424,6 +1527,7 @@ class KernelGroupConfig(_CommonConfig):
def reset(
self,
path: str,
only_children: bool,
config_bag: ConfigBag,
) -> None:
"""reset value for specified path"""
@ -1432,19 +1536,19 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag = config_bag.copy()
cconfig_bag.context = child
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
cconfig_bag.remove_validation()
# GROUP
option_bag = child.get_sub_option_bag(
subconfig = child.get_sub_config(
cconfig_bag,
path,
None,
False,
)[-1]
child.get_values().reset(option_bag)
validate_properties=False,
)
child.get_values().reset(subconfig)
def getconfig(
self,
@ -1499,7 +1603,7 @@ class KernelMixConfig(KernelGroupConfig):
def set_value(
self,
option_bag,
subconfig,
value,
only_config=False,
force_default=False,
@ -1530,10 +1634,10 @@ class KernelMixConfig(KernelGroupConfig):
)
)
for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy()
cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child
settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache)
properties = settings.get_context_properties()
cconfig_bag.properties = properties
cconfig_bag.permissives = settings.get_context_permissives()
try:
@ -1545,14 +1649,16 @@ class KernelMixConfig(KernelGroupConfig):
not force_default and not force_default_if_same
)
# MIX
moption_bag = obj.get_sub_option_bag(
moption_bag = obj.get_sub_config(
cconfig_bag,
option_bag.path,
option_bag.index,
validate_properties,
)[-1]
subconfig.path,
subconfig.index,
validate_properties=validate_properties,
)
if force_default_if_same:
if not child.get_values().hasvalue(option_bag.path):
if not child.get_values().hasvalue(
subconfig.path, index=subconfig.index
):
child_value = undefined
else:
child_value = child.get_value(moption_bag)
@ -1572,7 +1678,7 @@ class KernelMixConfig(KernelGroupConfig):
# pylint: disable=protected-access
ret.append(
PropertiesOptionError(
err._option_bag,
err.subconfig,
err.proptype,
err._settings,
err._opt_type,
@ -1585,12 +1691,12 @@ class KernelMixConfig(KernelGroupConfig):
try:
# MIX
moption_bag = self.get_sub_option_bag(
option_bag.config_bag,
option_bag.path,
option_bag.index,
not only_config,
)[-1]
moption_bag = self.get_sub_config(
subconfig.config_bag,
subconfig.path,
subconfig.index,
validate_properties=not only_config,
)
if only_config:
ret = super().set_value(
moption_bag,
@ -1619,37 +1725,37 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag.remove_validation()
if self.impl_type == "meta":
# MIX
option_bag = self.get_sub_option_bag(
subconfig = self.get_sub_config(
config_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
elif not only_children:
try:
# MIX
option_bag = self.get_sub_option_bag(
subconfig = self.get_sub_config(
rconfig_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
except AttributeError:
only_children = True
for child in self._impl_children:
rconfig_bag.context = child
try:
if self.impl_type == "meta":
moption_bag = option_bag
moption_bag = subconfig
moption_bag.config_bag = rconfig_bag
else:
# MIX
moption_bag = child.get_sub_option_bag(
moption_bag = child.get_sub_config(
rconfig_bag,
path,
None,
True,
)[-1]
validate_properties=True,
)
child.get_values().reset(moption_bag)
except AttributeError:
pass
@ -1660,8 +1766,8 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag,
)
if not only_children:
option_bag.config_bag = config_bag
self.get_values().reset(option_bag)
subconfig.config_bag = config_bag
self.get_values().reset(subconfig)
def new_config(
self,
@ -1788,6 +1894,10 @@ class KernelMetaConfig(KernelMixConfig):
raise TypeError(_("child must be a Config or MetaConfig"))
if descr is None:
descr = child.get_description()
if child.impl_getname() is None:
raise ConfigError(
_("children in MetaConfig must have name")
)
elif descr is not child.get_description():
raise ValueError(
_(
@ -1807,5 +1917,5 @@ class KernelMetaConfig(KernelMixConfig):
config,
):
if self._impl_descr is not config.get_description():
raise ValueError(_("metaconfig must " "have the same optiondescription"))
raise ValueError(_("metaconfig must have the same optiondescription"))
super().add_config(config)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -18,12 +18,30 @@
import weakref
from .i18n import _
from typing import Literal, Union, Optional
TiramisuErrorCode = Literal[
"option-dynamic",
"option-not-found",
"property-frozen",
"property-error",
"property-mandatory",
"leadership-group_type",
"leadership-wrong_property",
"leadership-force_default_on_freeze",
"leadership-greater",
"leadership-follower-greater",
"leadership-follower-callback-list",
]
def display_list(
lst,
*,
separator="and",
add_quote=False,
separator: str="and",
add_quote: bool=False,
sort: bool=True,
) -> str():
if not lst:
return '""'
@ -44,15 +62,13 @@ def display_list(
for l in lst:
if not isinstance(l, str):
l = str(l)
lst_.append(_(l))
lst__ = []
for l in lst_:
if add_quote and not l.startswith('"'):
l = '"{}"'.format(l)
lst__.append(l)
lst__.sort()
last = lst__[-1]
return ", ".join(lst__[:-1]) + _(" {} ").format(separator) + "{}".format(last)
lst_.append(_(l))
if sort:
lst_.sort()
last = lst_[-1]
return ", ".join(lst_[:-1]) + _(" {} ").format(separator) + "{}".format(last)
# Exceptions for an Option
@ -66,13 +82,11 @@ class PropertiesOptionError(AttributeError):
settings,
opt_type=None,
name=None,
orig_opt=None,
help_properties=None,
):
if opt_type:
self._opt_type = opt_type
self._name = name
self._orig_opt = orig_opt
else:
if subconfig.option.impl_is_optiondescription():
self._opt_type = "optiondescription"
@ -82,15 +96,28 @@ class PropertiesOptionError(AttributeError):
subconfig, with_quote=True
)
self._orig_opt = None
self._subconfig = subconfig
self.subconfig = subconfig
self.proptype = proptype
self.help_properties = help_properties
self._settings = settings
self.msg = None
if not self.help_properties:
self.help_properties = self.proptype
properties = list(self.help_properties)
if properties == ["frozen"]:
self.code = "property-frozen"
elif properties == ["mandatory"]:
self.code = "property-mandatory"
else:
self.code = "property-error"
super().__init__(None)
def set_orig_opt(self, opt):
self._orig_opt = opt
def display_properties(self, force_property=False, add_quote=True):
if force_property:
properties = self.proptype
else:
properties = self.help_properties
return display_list(list(properties), add_quote=add_quote)
def __str__(self):
# this part is a bit slow, so only execute when display
@ -98,42 +125,81 @@ class PropertiesOptionError(AttributeError):
return self.msg
if self._settings is None:
return "error"
if self.help_properties:
properties = list(self.help_properties)
else:
properties = list(self.proptype)
only_one = len(properties) == 1
properties_msg = display_list(properties, add_quote=True)
if only_one:
prop_msg = _("property")
else:
prop_msg = _("properties")
if properties == ["frozen"]:
arguments = [self._opt_type]
if self._orig_opt:
msg = _('cannot modify the {0} {1} because "{2}" has {3} {4}')
else:
msg = _("cannot modify the {0} {1} because has {2} {3}")
else:
arguments.append(
self._orig_opt.impl_get_display_name(subconfig, with_quote=True)
)
arguments.append(self._name)
index = self.subconfig.index
if index is not None:
arguments.append(index)
if self.code == "property-frozen":
if index is not None:
if self._orig_opt:
msg = _('cannot access to {0} {1} because "{2}" has {3} {4}')
else:
msg = _("cannot access to {0} {1} because has {2} {3}")
if self._orig_opt:
# FIXME _orig_opt ?
self.msg = msg.format(
self._opt_type,
self._orig_opt.impl_get_display_name(subconfig, with_quote=True),
self._name,
prop_msg,
properties_msg,
msg = _(
'cannot modify the {0} {1} at index "{2}" because {3} is frozen'
)
else:
self.msg = msg.format(self._opt_type, self._name, prop_msg, properties_msg)
msg = _(
'cannot modify the {0} {1} at index "{2}" because is frozen'
)
else:
if self._orig_opt:
msg = _("cannot modify the {0} {1} because {2} is frozen")
else:
msg = _("cannot modify the {0} {1} because is frozen")
elif self.code == "property-mandatory":
if index is not None:
if self._orig_opt:
msg = _(
'cannot access to {0} {1} at index "{2}" because {3} hasn\'t value'
)
else:
msg = _('{0} {1} at index "{2}" is mandatory but hasn\'t value')
else:
if self._orig_opt:
msg = _("cannot access to {0} {1} because {2} hasn't value")
else:
msg = _("{0} {1} is mandatory but hasn't value")
else:
if index is not None:
if self._orig_opt:
msg = _(
'cannot access to {0} {1} at index "{2}" because {3} has {4} {5}'
)
else:
msg = _(
'cannot access to {0} {1} at index "{2}" because has {3} {4}'
)
else:
if self._orig_opt:
msg = _("cannot access to {0} {1} because {2} has {3} {4}")
else:
msg = _("cannot access to {0} {1} because has {2} {3}")
only_one = len(self.help_properties) == 1
if only_one:
arguments.append(_("property"))
else:
arguments.append(_("properties"))
arguments.append(self.display_properties())
self.msg = msg.format(*arguments)
del self._opt_type, self._name
del self._settings, self._orig_opt
return self.msg
class AttributeOptionError(AttributeError):
def __init__(self, path: str, code: TiramisuErrorCode) -> None:
self.path = path
self.code = code
def __str__(self) -> str:
if self.code == "option-dynamic":
return _('cannot access to "{0}" it\'s a dynamic option').format(self.path)
return _('"{0}" is not an option').format(self.path)
# ____________________________________________________________
# Exceptions for a Config
class ConfigError(Exception):
@ -144,10 +210,22 @@ class ConfigError(Exception):
def __init__(
self,
exp,
ori_err=None,
*,
prefix: Optional[str] = None,
subconfig: Optional["Subconfig"]=None,
):
super().__init__(exp)
self.ori_err = ori_err
self.err_msg = exp
self.subconfig = subconfig
self.prefix = prefix
def __str__(self):
msg = self.prefix
if msg:
msg += ", {}".format(self.err_msg)
else:
msg = self.err_msg
return msg
class ConflictError(Exception):
@ -158,8 +236,74 @@ class ConflictError(Exception):
# ____________________________________________________________
# miscellaneous exceptions
class LeadershipError(Exception):
"problem with a leadership's value length"
pass
def __init__(
self,
subconfig: Union[str, "SubConfig"],
code,
*,
prop=None,
index=None,
length=None,
callback=None,
args=None,
kwargs=None,
ret=None,
):
if isinstance(subconfig, str):
self.path = self.display_name = subconfig
else:
self.path = subconfig.path
option = subconfig.option
self.display_name = option.impl_get_display_name(subconfig, with_quote=True)
self.code = code
if prop is not None:
self.prop = prop
if index is not None:
self.index = index
if length is not None:
self.length = length
if callback is not None:
self.callback = callback
if args is not None:
self.args = args
if kwargs is not None:
self.kwargs = kwargs
if ret is not None:
self.ret = ret
def __str__(self):
if self.code == "leadership-group_type":
return _('cannot set "group_type" attribute for the Leadership {0}').format(
self.display_name
)
if self.code == "leadership-wrong_property":
return _('the leader {0} cannot have "{1}" property').format(
self.display_name, self.prop
)
if self.code == "leadership-force_default_on_freeze":
return _(
'the leader {0} cannot have "force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
).format(self.display_name)
if self.code == "leadership-reduce":
return _("cannot reduce length of the leader {0}").format(self.display_name)
if self.code == "leadership-greater":
return _(
'index "{0}" is greater than the leadership length "{1}" for option {2}'
).format(self.index, self.length, self.display_name)
if self.code == "leadership-follower-greater":
return _(
"the follower option {0} has greater length ({1}) than the leader length ({2})"
).format(self.display_name, self.index, self.length)
if self.code == "leadership-follower-callback-list":
if self.args or self.kwargs:
return _(
'the "{0}" function with positional arguments "{1}" and keyword arguments "{2}" must not return a list ("{3}") for the follower option {4}'
).format(
self.callback, self.args, self.kwargs, self.ret, self.display_name
)
return _(
'the "{0}" function must not return a list ("{1}") for the follower option {2}'
).format(self.callback, self.ret, self.display_name)
class ConstError(TypeError):
@ -172,7 +316,9 @@ class _CommonError:
self.val = val
self.display_type = display_type
self.opt = weakref.ref(opt)
self.name = opt.impl_get_display_name(subconfig)
self.name = opt.impl_get_display_name(subconfig, with_quote=True)
if subconfig:
self.path = subconfig.path
self.err_msg = err_msg
self.index = index
super().__init__(self.err_msg)
@ -181,7 +327,9 @@ class _CommonError:
try:
msg = self.prefix
except AttributeError:
self.prefix = self.tmpl.format(self.val, _(self.display_type), self.name)
self.prefix = self.tmpl.format(
self.val, _(self.display_type), self.name, self.index
)
msg = self.prefix
if self.err_msg:
if msg:
@ -196,13 +344,20 @@ class _CommonError:
class ValueWarning(_CommonError, UserWarning):
tmpl = None
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
if ValueWarning.tmpl is None:
ValueWarning.tmpl = _('attention, "{0}" could be an invalid {1} for "{2}"')
if len(args) == 1 and not kwargs:
self.msg = args[0]
if kwargs.get("index") is None:
ValueWarning.tmpl = _(
'attention, "{0}" could be an invalid {1} for {2}'
)
else:
super().__init__(*args, **kwargs)
ValueWarning.tmpl = _(
'attention, "{0}" could be an invalid {1} for {2} at index "{3}"'
)
if list(kwargs) == ["msg"]:
self.msg = kwargs["msg"]
else:
super().__init__(**kwargs)
self.msg = None
def __str__(self):
@ -214,10 +369,20 @@ class ValueWarning(_CommonError, UserWarning):
class ValueOptionError(_CommonError, ValueError):
tmpl = None
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
if ValueOptionError.tmpl is None:
ValueOptionError.tmpl = _('"{0}" is an invalid {1} for "{2}"')
super().__init__(*args, **kwargs)
opt = kwargs.get("opt")
if opt and opt._do_not_display_value_in_error:
if kwargs.get("index") is None:
self.tmpl = _("{2} has an invalid {1}")
else:
self.tmpl = _('{2} at index "{3}" has an invalid {1}')
else:
if kwargs.get("index") is None:
self.tmpl = _('"{0}" is an invalid {1} for {2}')
else:
self.tmpl = _('"{0}" is an invalid {1} for {2} at index "{3}"')
super().__init__(**kwargs)
class ValueErrorWarning(ValueWarning):
@ -225,5 +390,45 @@ class ValueErrorWarning(ValueWarning):
def __init__(self, *args, **kwargs):
if ValueErrorWarning.tmpl is None:
ValueErrorWarning.tmpl = _('"{0}" is an invalid {1} for "{2}"')
ValueErrorWarning.tmpl = _('"{0}" is an invalid {1} for {2}')
super().__init__(*args, **kwargs)
class CancelParam(Exception):
def __init__(self, origin_path, current_path):
super().__init__()
self.origin_path = origin_path
self.current_path = current_path
def __ne__(self, value):
return value is None or value == ""
def __eq__(self, value):
return value is None or value == ""
def __bool__(self):
return False
class ValueErrorIndexes(ValueError):
def __init__(self, msg, indexes):
super().__init__(msg)
self.indexes = indexes
class Errors:
@staticmethod
def raise_carry_out_calculation_error(
subconfig, message, original_error, option=None, extra_keys=[]
):
if option is None:
option = subconfig.option
display_name = option.impl_get_display_name(subconfig, with_quote=True)
if original_error:
raise ConfigError(
message.format(display_name, original_error, *extra_keys), subconfig=subconfig,
) from original_error
raise ConfigError(message.format(display_name, extra_keys), subconfig=subconfig)
errors = Errors()

View file

@ -1,4 +1,4 @@
# Copyright (C) 2018-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2018-2026 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
@ -28,7 +28,7 @@ FUNCTION_WAITING_FOR_ERROR = []
def function_waiting_for_dict(function):
"""functions (calculation or validation) receive by default only the value of other options
all functions declared with this function recieve a dict with option informations
if you use this decoractor, it will recieve a dict with option informations
(value, name, ...)
"""
name = function.__name__
@ -38,8 +38,8 @@ def function_waiting_for_dict(function):
def function_waiting_for_error(function):
"""functions (calculation or validation) receive by default only the value of other options
set PropertyError too
"""functions (calculation or validation) receiven by default, only the value of other options
if you use this decoractor, it will pass PropertyError too
"""
name = function.__name__
if name not in FUNCTION_WAITING_FOR_ERROR:

View file

@ -1,5 +1,5 @@
# -*- coding: UTF-8 -*-
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"logger for tiramisu"
# Copyright (C) 2019-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2019-2026 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

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2014-2026 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

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2014-2026 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
@ -27,7 +27,7 @@ from itertools import chain
from ..i18n import _
from ..setting import undefined
from ..autolib import Calculation, ParamOption, ParamInformation, ParamSelfInformation
from ..autolib import Calculation, ParamOption, ParamSelfOption, ParamInformation, ParamSelfInformation
STATIC_TUPLE = frozenset()
@ -59,6 +59,7 @@ class Base:
"_dependencies",
"_dependencies_information",
"_identifiers_dependencies",
"could_conflict",
"__weakref__",
)
@ -88,9 +89,12 @@ class Base:
assert isinstance(properties, frozenset), _(
"invalid properties type {0} for {1}," " must be a frozenset"
).format(type(properties), name)
if not self.impl_is_optiondescription() and "novalidator" not in properties:
properties = properties | {"validator"}
_setattr = object.__setattr__
_setattr(self, "_name", name)
_setattr(self, "_informations", {"doc": doc})
_setattr(self, "could_conflict", [])
for prop in properties:
if not isinstance(prop, str):
if not isinstance(prop, Calculation):
@ -100,9 +104,7 @@ class Base:
"Calculation"
).format(type(prop), name)
)
for param in chain(prop.params.args, prop.params.kwargs.values()):
if isinstance(param, ParamOption):
param.option._add_dependency(self)
self.value_dependency(prop, type_="property")
if properties:
_setattr(self, "_properties", properties)
self.set_informations(informations)
@ -146,11 +148,12 @@ class Base:
def _add_dependency(
self,
option,
type_,
is_identifier: bool = False,
) -> None:
woption = weakref.ref(option)
options = self.get_dependencies(None)
options.add(woption)
options.add((type_ == "default", woption))
self._dependencies = tuple(
options
) # pylint: disable=attribute-defined-outside-init
@ -186,6 +189,7 @@ class Base:
else:
dico = tuple([keys, tuple(dico.values())])
_setattr(self, "_informations", dico)
_setattr(self, "could_conflict", tuple(self.could_conflict))
extra = getattr(self, "_extra", None)
if extra is not None:
_setattr(
@ -389,12 +393,22 @@ class BaseOption(Base):
self,
value: Any,
is_identifier: bool = False,
type_: str = 'default'
) -> Any:
if not isinstance(is_identifier, bool):
raise Exception()
"""parse dependancy to add dependencies"""
for param in chain(value.params.args, value.params.kwargs.values()):
if isinstance(param, ParamOption):
# pylint: disable=protected-access
param.option._add_dependency(self, is_identifier=is_identifier)
if is_identifier:
_type_ = "identifier"
else:
_type_ = type_
param.option._add_dependency(self, _type_, is_identifier=is_identifier)
self._has_dependency = True
elif isinstance(param, ParamSelfOption) and not param.dynamic:
self._add_dependency(self, "self")
self._has_dependency = True
elif isinstance(param, ParamInformation):
dest = self

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -30,6 +30,7 @@ class BoolOption(Option):
__slots__ = tuple()
_type = "boolean"
_t_type = _("boolean")
def validate(
self,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -23,27 +23,22 @@
from ipaddress import ip_address
from ..i18n import _
from .option import Option
from .stroption import StrOption
class BroadcastOption(Option):
class BroadcastOption(StrOption):
"""represents the choice of a broadcast"""
__slots__ = tuple()
_type = "broadcast address"
_t_type = _("broadcast address")
def validate(
self,
value: str,
) -> None:
"""validate"""
if not isinstance(value, str):
raise ValueError(_("invalid string"))
if value.count(".") != 3:
raise ValueError()
for val in value.split("."):
if val.startswith("0") and len(val) > 1:
raise ValueError()
super().validate(value)
try:
ip_address(value)
except ValueError as err:

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -21,11 +21,12 @@
"""ChoiceOption
"""
from typing import Any
from itertools import chain
from ..setting import undefined
from ..i18n import _
from .option import Option
from ..autolib import Calculation, get_calculated_value
from ..autolib import Calculation, get_calculated_value, ParamOption
from ..error import ConfigError, display_list
@ -37,12 +38,15 @@ class ChoiceOption(Option):
__slots__ = tuple()
_type = "choice"
_t_type = _("choice")
def __init__(self, name, doc, values, *args, **kwargs):
"""
:param values: is a list of values the option can possibly take
"""
if not isinstance(values, (Calculation, tuple)):
if isinstance(values, Calculation):
self.value_dependency(values, type_="choice")
elif not isinstance(values, tuple):
raise TypeError(
_("values must be a tuple or a calculation for {0}").format(name)
)
@ -69,7 +73,8 @@ class ChoiceOption(Option):
raise ConfigError(
_('the calculated values "{0}" for "{1}" is not a list' "").format(
values, self.impl_getname()
)
),
subconfig=subconfig,
)
return values

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -31,6 +31,7 @@ class DateOption(StrOption):
__slots__ = tuple()
_type = "date"
_t_type = _("date")
def validate(self, value: str) -> None:
super().validate(value)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -21,6 +21,7 @@
"""DomainnameOption
"""
import re
import socket
from ipaddress import ip_interface
from typing import Any, Optional, List
@ -42,6 +43,7 @@ class DomainnameOption(StrOption):
__slots__ = tuple()
_type = "domain name"
_t_type = _("domain name")
def __init__(
self,
@ -53,12 +55,18 @@ class DomainnameOption(StrOption):
type: str = "domainname",
allow_without_dot: bool = False,
allow_startswith_dot: bool = False,
test_existence: bool = False,
_extra: dict = None,
**kwargs,
) -> None:
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if _extra is None:
extra = {}
else:
extra = _extra
if type not in ["netbios", "hostname", "domainname"]:
raise ValueError(_("unknown type {0} for hostname").format(type))
extra = {"_dom_type": type}
extra["type"] = type
if not isinstance(allow_ip, bool):
raise ValueError(_("allow_ip must be a boolean"))
if not isinstance(allow_cidr_network, bool):
@ -67,7 +75,8 @@ class DomainnameOption(StrOption):
raise ValueError(_("allow_without_dot must be a boolean"))
if not isinstance(allow_startswith_dot, bool):
raise ValueError(_("allow_startswith_dot must be a boolean"))
extra["_allow_without_dot"] = allow_without_dot
extra["allow_without_dot"] = allow_without_dot
extra["test_existence"] = test_existence
if type == "domainname":
if allow_without_dot:
min_time = 0
@ -76,14 +85,20 @@ class DomainnameOption(StrOption):
regexp = r"((?!-)[a-z0-9-]{{{1},{0}}}\.){{{1},}}[a-z0-9-]{{1,{0}}}".format(
self._get_len(type), min_time
)
else:
regexp = r"((?!-)[a-z0-9-]{{1,{0}}})".format(self._get_len(type))
msg = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed'
)
msg_warning = _(
'must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are recommanded'
)
else:
regexp = r"((?!-)[a-z0-9-]{{1,{0}}})".format(self._get_len(type))
msg = _(
'must start with lowercase characters followed by lowercase characters, number and "-" characters are allowed'
)
msg_warning = _(
'must start with lowercase characters followed by lowercase characters, number and "-" characters are recommanded'
)
if allow_ip:
msg = _("could be a IP, otherwise {}").format(msg)
msg_warning = _("could be a IP, otherwise {}").format(msg_warning)
@ -105,15 +120,15 @@ class DomainnameOption(StrOption):
name,
doc,
)
extra["_allow_ip"] = allow_ip
extra["allow_ip"] = allow_ip
if allow_cidr_network:
extra["_network"] = NetworkOption(
name,
doc,
cidr=True,
)
extra["_allow_cidr_network"] = allow_cidr_network
extra["_allow_startswith_dot"] = allow_startswith_dot
extra["allow_cidr_network"] = allow_cidr_network
extra["allow_startswith_dot"] = allow_startswith_dot
super().__init__(
name,
@ -137,13 +152,13 @@ class DomainnameOption(StrOption):
_("invalid length (max {0})" "").format(part_name_length)
)
part_name_length = self._get_len(self.impl_get_extra("_dom_type"))
if self.impl_get_extra("_dom_type") == "domainname":
if not self.impl_get_extra("_allow_without_dot") and not "." in value:
part_name_length = self._get_len(self.impl_get_extra("type"))
if self.impl_get_extra("type") == "domainname":
if not self.impl_get_extra("allow_without_dot") and not "." in value:
raise ValueError(_("must have dot"))
if len(value) > 255:
raise ValueError(_("invalid length (max 255)"))
if self.impl_get_extra("_allow_startswith_dot") and value.startswith("."):
if self.impl_get_extra("allow_startswith_dot") and value.startswith("."):
val = value[1:]
else:
val = value
@ -155,10 +170,22 @@ class DomainnameOption(StrOption):
_valid_length(dom)
else:
_valid_length(value)
self._validate_domain_resolution(value)
def _validate_domain_resolution(self, value: str) -> None:
if not value.startswith(".") and self.impl_get_extra("test_existence") is True:
try:
socket.gethostbyname(value)
except socket.gaierror as err:
raise ValueError(_("DNS resolution failed").format(value)) from err
except Exception as err:
raise ValueError(
_("error resolving DNS: {1}").format(value, err)
) from err
def _validate_ip_network(self, value: str) -> None:
allow_ip = self.impl_get_extra("_allow_ip")
allow_cidr_network = self.impl_get_extra("_allow_cidr_network")
allow_ip = self.impl_get_extra("allow_ip")
allow_cidr_network = self.impl_get_extra("allow_cidr_network")
if allow_ip is False and allow_cidr_network is False:
raise ValueError(_("must not be an IP"))
if allow_ip is True:
@ -184,7 +211,7 @@ class DomainnameOption(StrOption):
def _second_level_validation_domain(self, value: str, warnings_only: bool) -> None:
if self.impl_get_extra("_has_upper").search(value):
raise ValueError(_("some characters are uppercase"))
if self.impl_get_extra("_allow_startswith_dot") and value.startswith("."):
if self.impl_get_extra("allow_startswith_dot") and value.startswith("."):
val = value[1:]
else:
val = value
@ -200,8 +227,8 @@ class DomainnameOption(StrOption):
def _second_level_validation_ip_network(
self, value: str, warnings_only: bool
) -> None:
allow_ip = self.impl_get_extra("_allow_ip")
allow_cidr_network = self.impl_get_extra("_allow_cidr_network")
allow_ip = self.impl_get_extra("allow_ip")
allow_cidr_network = self.impl_get_extra("allow_cidr_network")
# it's an IP so validate with IPOption
if allow_ip is True and allow_cidr_network is False:
try:

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -31,7 +31,6 @@ from ..i18n import _
from .optiondescription import OptionDescription
from .baseoption import BaseOption
from ..setting import ConfigBag, undefined
from ..error import ConfigError
from ..autolib import Calculation, get_calculated_value
@ -82,6 +81,9 @@ class DynOptionDescription(OptionDescription):
identifier = identifier.replace(".", "_")
return identifier
def name_could_conflict(self, dynchild, child):
return child.impl_getname().startswith(dynchild.impl_getname())
def impl_is_dynoptiondescription(self) -> bool:
return True
@ -104,6 +106,8 @@ class DynOptionDescription(OptionDescription):
parent: "SubConfig",
*,
uncalculated: bool = False,
from_display_name: bool = False,
convert: bool = False,
) -> List[str]:
"""get dynamic identifiers"""
subconfig = parent.get_child(
@ -111,6 +115,7 @@ class DynOptionDescription(OptionDescription):
None,
False,
properties=None,
check_dynamic_without_identifiers=False,
)
identifiers = self._identifiers
if isinstance(identifiers, list):
@ -124,16 +129,18 @@ class DynOptionDescription(OptionDescription):
)[0]
if values is None:
values = []
values_ = []
if __debug__:
if not isinstance(values, list):
if not from_display_name:
name = self.impl_get_display_name(subconfig, with_quote=True)
else:
name = self.impl_getname()
raise ValueError(
_(
"DynOptionDescription identifiers for option {0}, is not a list ({1})"
).format(
self.impl_get_display_name(subconfig, with_quote=True), values
)
).format(name, values)
)
values_ = []
for val in values:
cval = self.convert_identifier_to_path(val)
if not isinstance(cval, str) or re.match(NAME_REGEXP, cval) is None:
@ -143,9 +150,15 @@ class DynOptionDescription(OptionDescription):
cval, self.impl_get_display_name(subconfig, with_quote=True)
)
)
elif convert:
values_.append(cval)
else:
values_.append(val)
if __debug__ and len(values_) > len(set(values_)):
if (
__debug__
and "demoting_error_warning" not in subconfig.config_bag.properties
and len(values_) > len(set(values_))
):
raise ValueError(
_(
'DynOptionDescription "{0}" identifiers return a list with same values "{1}"'

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -34,3 +34,4 @@ class EmailOption(RegexpOption):
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"
)
_type = "email address"
_t_type = _("email address")

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -32,6 +32,7 @@ class FilenameOption(StrOption):
__slots__ = tuple()
_type = "file name"
_t_type = _("file name")
def __init__(
self,
@ -52,9 +53,9 @@ class FilenameOption(StrOption):
if typ not in ["file", "directory"]:
raise ValueError(f'unknown type "{typ}" for "{name}"')
extra = {
"_allow_relative": allow_relative,
"_test_existence": test_existence,
"_types": types,
"allow_relative": allow_relative,
"test_existence": test_existence,
"types": types,
}
super().__init__(name, *args, extra=extra, **kwargs)
@ -63,10 +64,10 @@ class FilenameOption(StrOption):
value: str,
) -> None:
super().validate(value)
if not self.impl_get_extra("_allow_relative") and not value.startswith("/"):
if not self.impl_get_extra("allow_relative") and not value.startswith("/"):
raise ValueError(_('must starts with "/"'))
if value is not None and self.impl_get_extra("_test_existence"):
types = self.impl_get_extra("_types")
if value is not None and self.impl_get_extra("test_existence"):
types = self.impl_get_extra("types")
file = Path(value)
found = False
if "file" in types and file.is_file():
@ -74,8 +75,12 @@ class FilenameOption(StrOption):
if not found and "directory" in types and file.is_dir():
found = True
if not found:
translated_types = [
{"file": _("file"), "directory": _("directory")}.get(typ)
for typ in types
]
raise ValueError(
_('cannot find {0} "{1}"').format(
display_list(types, separator="or"), value
_("cannot find this {0}").format(
display_list(translated_types, separator="or"), value
)
)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -30,6 +30,7 @@ class FloatOption(Option):
__slots__ = tuple()
_type = "float"
_t_type = _("float")
def validate(self, value: float) -> None:
if not isinstance(value, float):

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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,13 +29,26 @@ class IntOption(Option):
"represents a choice of an integer"
__slots__ = tuple()
_type = "integer"
_t_type = _("integer")
def __init__(self, *args, min_number=None, max_number=None, **kwargs):
def __init__(
self,
*args,
min_number=None,
max_number=None,
min_integer=None,
max_integer=None,
**kwargs,
):
extra = {}
if min_number is not None:
extra["min_number"] = min_number
extra["min_integer"] = min_number
if min_integer is not None:
extra["min_integer"] = min_integer
if max_number is not None:
extra["max_number"] = max_number
extra["max_integer"] = max_number
if max_integer is not None:
extra["max_integer"] = max_integer
super().__init__(*args, extra=extra, **kwargs)
def validate(
@ -43,20 +56,20 @@ class IntOption(Option):
value: int,
) -> None:
if not isinstance(value, int):
raise ValueError()
raise ValueError(_("it's not an integer"))
def second_level_validation(self, value, warnings_only):
min_number = self.impl_get_extra("min_number")
if min_number is not None and value < min_number:
min_integer = self.impl_get_extra("min_integer")
if min_integer is not None and value < min_integer:
if warnings_only:
msg = _('value should be equal or greater than "{0}"')
else:
msg = _('value must be equal or greater than "{0}"')
raise ValueError(msg.format(min_number))
max_number = self.impl_get_extra("max_number")
if max_number is not None and value > max_number:
raise ValueError(msg.format(min_integer))
max_integer = self.impl_get_extra("max_integer")
if max_integer is not None and value > max_integer:
if warnings_only:
msg = _('value should be less than "{0}"')
else:
msg = _('value must be less than "{0}"')
raise ValueError(msg.format(max_number))
raise ValueError(msg.format(max_integer))

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -31,6 +31,7 @@ class IPOption(StrOption):
__slots__ = tuple()
_type = "IP"
_t_type = _("IP")
def __init__(
self,
@ -43,9 +44,9 @@ class IPOption(StrOption):
):
if extra is None:
extra = {}
extra["_private_only"] = private_only
extra["_allow_reserved"] = allow_reserved
extra["_cidr"] = cidr
extra["private_only"] = private_only
extra["allow_reserved"] = allow_reserved
extra["cidr"] = cidr
super().__init__(*args, extra=extra, **kwargs)
def _validate_cidr(self, value):
@ -53,6 +54,9 @@ class IPOption(StrOption):
ip_obj = ip_interface(value)
except ValueError as err:
raise ValueError() from err
self._second_level_cidr(ip_obj)
def _second_level_cidr(self, ip_obj):
if ip_obj.ip == ip_obj.network.network_address:
raise ValueError(_("it's in fact a network address"))
if ip_obj.ip == ip_obj.network.broadcast_address:
@ -66,7 +70,7 @@ class IPOption(StrOption):
def validate(self, value: str) -> None:
super().validate(value)
if self.impl_get_extra("_cidr"):
if self.impl_get_extra("cidr"):
if "/" not in value:
raise ValueError(_('CIDR address must have a "/"'))
self._validate_cidr(value)
@ -75,13 +79,13 @@ class IPOption(StrOption):
def second_level_validation(self, value: str, warnings_only: bool) -> None:
ip_obj = ip_interface(value)
if not self.impl_get_extra("_allow_reserved") and ip_obj.is_reserved:
if not self.impl_get_extra("allow_reserved") and ip_obj.is_reserved:
if warnings_only:
msg = _("shouldn't be reserved IP")
else:
msg = _("mustn't be reserved IP")
raise ValueError(msg)
if self.impl_get_extra("_private_only") and not ip_obj.is_private:
if self.impl_get_extra("private_only") and not ip_obj.is_private:
if warnings_only:
msg = _("should be private IP")
else:

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"Leadership support"
# Copyright (C) 2014-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2014-2026 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
@ -51,9 +51,7 @@ class Leadership(OptionDescription):
**kwargs,
) -> None:
if "group_type" in kwargs:
raise LeadershipError(
_('cannot set "group_type" attribute for a Leadership')
)
raise LeadershipError(name, "leadership-group_type")
super().__init__(
name,
doc,
@ -77,7 +75,7 @@ class Leadership(OptionDescription):
# remove empty property for follower
child._properties = frozenset(child._properties - {"empty", "unique"})
followers.append(child)
child._add_dependency(self)
child._add_dependency(self, "leadership")
child._leadership = weakref.ref(self)
if __debug__:
leader = children[0]
@ -85,9 +83,7 @@ class Leadership(OptionDescription):
if prop not in ALLOWED_LEADER_PROPERTIES and not isinstance(
prop, Calculation
):
raise LeadershipError(
_('leader cannot have "{}" property').format(prop)
)
raise LeadershipError(name, "leadership-wrong_property", prop=prop)
def _check_child_is_valid(
self,
@ -112,7 +108,7 @@ class Leadership(OptionDescription):
if not child.impl_is_multi():
raise ValueError(
_(
"only multi option allowed in leadership {0} but option "
"only multi option are allowed in leadership {0} but option "
"{1} is not a multi"
""
).format(

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2020-2026 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
@ -32,3 +32,4 @@ class MACOption(RegexpOption):
__slots__ = tuple()
_regexp = re.compile(r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$")
_type = "mac address"
_t_type = _("mac address")

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -30,6 +30,7 @@ class NetmaskOption(StrOption):
__slots__ = tuple()
_type = "netmask address"
_t_type = _("netmask address")
def validate(self, value: str) -> None:
super().validate(value)

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -23,36 +23,22 @@
from ipaddress import ip_network
from ..i18n import _
from .ipoption import IPOption
from .stroption import StrOption
class NetworkOption(StrOption):
class NetworkOption(IPOption):
"represents the choice of a network"
__slots__ = tuple()
_type = "network address"
_t_type = _("network address")
def __init__(self, *args, cidr=False, **kwargs):
extra = {"_cidr": cidr}
super().__init__(*args, extra=extra, **kwargs)
super().__init__(*args, cidr=cidr, **kwargs)
def validate(self, value: str) -> None:
super().validate(value)
if value.count(".") != 3:
raise ValueError()
cidr = self.impl_get_extra("_cidr")
if cidr:
if "/" not in value:
raise ValueError(_("must use CIDR notation"))
value_ = value.split("/")[0]
else:
value_ = value
for val in value_.split("."):
if val.startswith("0") and len(val) > 1:
raise ValueError()
try:
ip_network(value)
except ValueError as err:
raise ValueError() from err
def _second_level_cidr(self, ip_obj):
if ip_obj.ip != ip_obj.network.network_address:
raise ValueError(_("it's not a network address"))
def second_level_validation(self, value: str, warnings_only: bool) -> None:
if ip_network(value).network_address.is_reserved:

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"option types and option description"
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -53,6 +53,8 @@ class Option(BaseOption):
"_choice_values_params",
)
_type = None
_t_type = None
_do_not_display_value_in_error = False
def __init__(
self,
@ -127,11 +129,6 @@ class Option(BaseOption):
def test_multi_value(value):
if isinstance(value, Calculation):
return
# option_bag = OptionBag(self,
# None,
# undefined,
# properties=None,
# )
try:
self.validate(value)
self.validate_with_option(
@ -183,16 +180,19 @@ class Option(BaseOption):
# undefined,
# properties=None,
# )
self_properties = getattr(self, "_properties", {})
self.impl_validate(
None,
default,
loaded=True,
self_properties=self_properties,
)
self.impl_validate(
None,
default,
check_error=False,
loaded=True,
self_properties=self_properties,
)
self.value_dependencies(default)
if (is_multi and default != []) or (not is_multi and default is not None):
@ -215,8 +215,10 @@ class Option(BaseOption):
"""is a dynsymlinkoption?"""
return False
def get_type(self) -> str:
def get_type(self, translation=True) -> str:
"""get the type of option"""
if translation:
return self._t_type
return self._type
def impl_getdefault(self) -> Any:
@ -261,14 +263,20 @@ class Option(BaseOption):
*,
check_error: bool = True,
loaded: bool = False,
self_properties: frozenset = frozenset(),
) -> bool:
"""Return True if value is really valid
If not validate or invalid return it returns False
"""
if check_error:
if subconfig:
config_properties = subconfig.config_bag.properties
self_properties = subconfig.properties
else:
config_properties = {"validator"}
if (
check_error
and subconfig
and not "validator" in subconfig.config_bag.properties
"validator" not in config_properties
or "validator" not in self_properties
):
return False
if subconfig:
@ -277,17 +285,18 @@ class Option(BaseOption):
force_index = None
is_warnings_only = getattr(self, "_warnings_only", False)
def _is_not_unique(value):
# if set(value) has not same length than value
def _is_not_unique(current_value, values):
if current_value is None:
return
if not subconfig or not check_error or "unique" not in subconfig.properties:
return
lvalue = [val for val in value if val is not None]
if len(set(lvalue)) == len(lvalue):
return
for idx, val in enumerate(value):
if val not in value[idx + 1 :]:
continue
raise ValueError(_('the value "{}" is not unique' "").format(val))
indexes = [
index for index, value in enumerate(values) if value == current_value
]
if len(indexes) > 1:
raise ValueError(
_('the value "{}" is not unique' "").format(current_value)
)
def calculation_validator(
val,
@ -329,12 +338,12 @@ class Option(BaseOption):
except ValueWarning as warn:
warnings.warn_explicit(
ValueWarning(
subconfig,
val,
_(self.get_type()),
self,
str(warn),
_index,
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(warn),
index=_index,
),
ValueWarning,
self.__class__.__name__,
@ -349,7 +358,7 @@ class Option(BaseOption):
if _value is None:
return
if isinstance(_value, list):
raise ValueError(_("which must not be a list"))
raise ValueError(_("it must not be a list"))
if isinstance(_value, Calculation) and not subconfig:
return
# option validation
@ -370,12 +379,12 @@ class Option(BaseOption):
if is_warnings_only:
warnings.warn_explicit(
ValueWarning(
subconfig,
_value,
_(self.get_type()),
self,
str(err),
_index,
subconfig=subconfig,
val=_value,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=_index,
),
ValueWarning,
self.__class__.__name__,
@ -390,29 +399,40 @@ class Option(BaseOption):
_index,
)
val = value
err_index = force_index
try:
ret = True
if not self.impl_is_multi():
try:
do_validation(
val,
value,
None,
)
except ValueError as err:
self.validate_parse_error(value, err_index, err, subconfig)
ret = False
elif force_index is not None:
if self.impl_is_submulti():
if not isinstance(value, list):
raise ValueError(_("which must be a list"))
raise ValueError(_("it must be a list"))
for val in value:
try:
do_validation(
val,
force_index,
)
_is_not_unique(value)
_is_not_unique(val, value)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
else:
try:
do_validation(
val,
value,
force_index,
)
except ValueError as err:
self.validate_parse_error(value, err_index, err, subconfig)
ret = False
elif isinstance(value, Calculation) and not subconfig:
pass
elif self.impl_is_submulti():
@ -424,36 +444,57 @@ class Option(BaseOption):
_('which "{}" must be a list of list' "").format(lval)
)
for val in lval:
try:
do_validation(val, err_index)
_is_not_unique(lval)
_is_not_unique(val, lval)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
elif not isinstance(value, list):
raise ValueError(_("which must be a list"))
msg = ValueError(_("it must be a list"))
self.validate_parse_error(value, None, msg, subconfig)
ret = False
else:
# FIXME suboptimal, not several time for whole=True!
for err_index, val in enumerate(value):
try:
do_validation(
val,
err_index,
)
_is_not_unique(value)
_is_not_unique(val, value)
except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
# return False
return ret
def validate_parse_error(self, val, index, err, subconfig):
if (
not subconfig
or "demoting_error_warning" not in subconfig.config_bag.properties
):
raise ValueOptionError(
subconfig, val, _(self.get_type()), self, str(err), err_index
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=index,
) from err
warnings.warn_explicit(
ValueErrorWarning(
subconfig, val, _(self.get_type()), self, str(err), err_index
subconfig=subconfig,
val=val,
display_type=_(self.get_type()),
opt=self,
err_msg=str(err),
index=index,
),
ValueErrorWarning,
self.__class__.__name__,
0,
)
return False
return True
def validate_with_option(
self,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2014-2026 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 ..setting import ConfigBag, groups, undefined, owners, Undefined
from .baseoption import BaseOption
# from .syndynoption import SubDynOptionDescription, SynDynOptionDescription
from ..error import ConfigError, ConflictError
from ..error import ConfigError, ConflictError, AttributeOptionError, PropertiesOptionError
class CacheOptionDescription(BaseOption):
@ -106,7 +106,24 @@ class CacheOptionDescription(BaseOption):
if "force_store_value" in properties:
force_store_values.append(option)
if option.impl_is_readonly():
raise ConflictError(_("duplicate option: {0}").format(option))
previous_path = option.impl_getpath()
if "." in previous_path:
previous_path = _('"{0}" option description').format(
previous_path.rsplit(".", 1)[0]
)
else:
previous_path = _("root option description")
if currpath:
current_path = _('"{0}" option description').format(
".".join(currpath)
)
else:
current_path = _("root option description")
raise ConflictError(
_('option "{0}" is include in {1} but is also in {2}').format(
option.impl_get_display_name(None), current_path, previous_path
)
)
if not self.impl_is_readonly() and display_name:
option._display_name_function = (
display_name # pylint: disable=protected-access
@ -147,6 +164,7 @@ class CacheOptionDescription(BaseOption):
parent,
allow_dynoption=True,
)
try:
if doption.impl_is_dynoptiondescription():
new_parents.extend(
parent.dyn_to_subconfig(
@ -159,22 +177,26 @@ class CacheOptionDescription(BaseOption):
parent.get_child(
doption,
None,
True,
name=name,
validate_properties=True,
)
)
except PropertiesOptionError:
continue
parents = new_parents
subconfigs = new_parents
else:
try:
subconfigs = [
context.get_sub_config(
config_bag,
option.impl_getpath(),
None,
properties=None,
validate_properties=False,
validate_properties=True,
)
]
except PropertiesOptionError:
continue
if option.impl_is_follower():
for follower_subconfig in subconfigs:
@ -189,32 +211,14 @@ class CacheOptionDescription(BaseOption):
idx_follower_subconfig = parent.get_child(
follower_subconfig.option,
index,
validate_properties=False,
)
value = values.get_value(idx_follower_subconfig)[0]
if value is None:
continue
values.set_storage_value(
follower_subconfig.path,
index,
value,
owners.forced,
validate_properties=True,
)
values.set_force_store_value(idx_follower_subconfig)
else:
for subconfig in subconfigs:
subconfig.properties = frozenset()
value = values.get_value(subconfig)[0]
if value is None:
continue
if values.hasvalue(subconfig.path):
continue
values.set_storage_value(
subconfig.path,
None,
value,
owners.forced,
)
values.set_force_store_value(subconfig)
class OptionDescriptionWalk(CacheOptionDescription):
@ -232,19 +236,20 @@ class OptionDescriptionWalk(CacheOptionDescription):
def get_child_not_dynamic(
self,
name,
allow_dynoption,
name: str,
allow_dynoption: bool,
parent: "SubConfig",
):
if name in self._children[0]: # pylint: disable=no-member
option = self._children[1][
self._children[0].index(name)
] # pylint: disable=no-member
if option.impl_is_dynoptiondescription() and not allow_dynoption:
raise AttributeError(
_(
'unknown option "{0}" in root optiondescription (it\'s a dynamic option)'
).format(name)
)
if parent.path:
path = parent.path + "." + name
else:
path = name
raise AttributeOptionError(path, "option-dynamic")
return option
def get_child(
@ -261,6 +266,7 @@ class OptionDescriptionWalk(CacheOptionDescription):
option = self.get_child_not_dynamic(
name,
allow_dynoption,
parent,
)
if option:
return option
@ -274,45 +280,16 @@ class OptionDescriptionWalk(CacheOptionDescription):
if not with_identifier:
return child
return identifier, child
if self.impl_get_group_type() == groups.root: # pylint: disable=no-member
raise AttributeError(
_('unknown option "{0}" in root optiondescription').format(name)
)
raise AttributeError(
_('unknown option "{0}" in optiondescription {1}').format(
name, self.impl_get_display_name(parent, with_quote=True)
)
)
if parent.path is None:
path = name
else:
path = parent.path + "." + name
raise AttributeOptionError(path, "option-not-found")
def get_children(self) -> List[BaseOption]:
"""get children"""
return self._children[1]
def get_children_recursively(
self,
bytype: Optional[BaseOption],
byname: Optional[str],
config_bag: ConfigBag,
self_opt: BaseOption = None,
*,
option_identifiers: Optional[list] = None,
) -> Iterator[Union[BaseOption]]:
"""get children recursively"""
if self_opt is None:
self_opt = self
for option in self_opt.get_children():
if option.impl_is_optiondescription():
for subopt in option.get_children_recursively(
bytype,
byname,
config_bag,
):
yield subopt
elif (byname is None or option.impl_getname() == byname) and (
bytype is None or isinstance(option, bytype)
):
yield option
class OptionDescription(OptionDescriptionWalk):
"""Config's schema (organisation, group) and container of Options
@ -345,35 +322,38 @@ class OptionDescription(OptionDescriptionWalk):
properties=properties,
)
child_names = []
if __debug__:
dynopt_names = []
fix_child_names = []
dynopts = []
for child in children:
name = child.impl_getname()
child_names.append(name)
if __debug__ and child.impl_is_dynoptiondescription():
dynopt_names.append(name)
if child.impl_is_dynoptiondescription():
dynopts.append(child)
else:
fix_child_names.append(name)
# before sorting
children_ = (tuple(child_names), tuple(children))
if __debug__:
# better performance like this
child_names.sort()
fix_child_names.sort()
old = None
for child in child_names:
if child == old:
for child_name in fix_child_names:
if child_name == old:
raise ConflictError(
_("duplicate option name: " '"{0}"').format(child)
_('the option name "{0}" is duplicate in "{1}"').format(
child_name, self.impl_get_display_name(None)
)
if dynopt_names:
for dynopt in dynopt_names:
if child != dynopt and child.startswith(dynopt):
raise ConflictError(
_(
'the option\'s name "{0}" start as the dynoptiondescription\'s name "{1}"'
).format(child, dynopt)
)
old = child
old = child_name
for dynopt in dynopts:
if dynopt.could_conflict:
continue
for child in children:
if child != dynopt and dynopt.name_could_conflict(dynopt, child):
dynopt.could_conflict.append(weakref.ref(child))
child.could_conflict.append(weakref.ref(dynopt))
break
self._children = children_
# the group_type is useful for filtering OptionDescriptions in a config
self._group_type = None

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -31,6 +31,8 @@ class PasswordOption(StrOption):
__slots__ = tuple()
_type = "password"
_t_type = _("password")
_do_not_display_value_in_error = True
def __init__(self, *args, min_len=None, max_len=None, forbidden_char=[], **kwargs):
extra = {}

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2023-2026 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
@ -37,6 +37,7 @@ class PermissionsOption(IntOption):
__slots__ = tuple()
perm_re = re.compile(r"^[0-7]{3,4}$")
_type = "unix file permissions"
_t_type = _("unix file permissions")
def __init__(
self,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -20,8 +20,6 @@
# ____________________________________________________________
"""PortOption
"""
import re
from ..i18n import _
from .stroption import StrOption
@ -38,8 +36,8 @@ class PortOption(StrOption):
"""
__slots__ = tuple()
port_re = re.compile(r"^[0-9]*$")
_type = "port"
_t_type = _("port")
def __init__(
self,
@ -50,15 +48,21 @@ class PortOption(StrOption):
allow_registred: bool = True,
allow_protocol: bool = False,
allow_private: bool = False,
_extra: dict = None,
**kwargs,
) -> None:
extra = {
"_allow_range": allow_range,
"_allow_protocol": allow_protocol,
"_min_value": None,
"_max_value": None,
}
if _extra is None:
extra = {}
else:
extra = _extra
extra["allow_range"] = allow_range
extra["allow_protocol"] = allow_protocol
extra["allow_zero"] = allow_zero
extra["allow_wellknown"] = allow_wellknown
extra["allow_registred"] = allow_registred
extra["allow_private"] = allow_private
extra["_min_value"] = None
extra["_max_value"] = None
ports_min = [0, 1, 1024, 49152]
ports_max = [0, 1023, 49151, 65535]
is_finally = False
@ -82,11 +86,11 @@ class PortOption(StrOption):
def validate(self, value: str) -> None:
super().validate(value)
if self.impl_get_extra("_allow_protocol") and (
if self.impl_get_extra("allow_protocol") and (
value.startswith("tcp:") or value.startswith("udp:")
):
value = [value[4:]]
elif self.impl_get_extra("_allow_range") and ":" in str(value):
elif self.impl_get_extra("allow_range") and ":" in str(value):
value = value.split(":")
if len(value) != 2:
raise ValueError(_("range must have two values only"))
@ -98,11 +102,11 @@ class PortOption(StrOption):
value = [value]
for val in value:
if not self.port_re.search(val):
if not val.isdecimal():
raise ValueError()
def second_level_validation(self, value: str, warnings_only: bool) -> None:
if self.impl_get_extra("_allow_protocol") and (
if self.impl_get_extra("allow_protocol") and (
value.startswith("tcp:") or value.startswith("udp:")
):
value = [value[4:]]

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -31,6 +31,7 @@ class StrOption(Option):
__slots__ = tuple()
_type = "string"
_t_type = _("string")
def validate(
self,
@ -38,7 +39,7 @@ class StrOption(Option):
) -> None:
"""validation"""
if not isinstance(value, str):
raise ValueError()
raise ValueError(_("it's not a string"))
class RegexpOption(StrOption):

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -22,7 +22,6 @@
"""
from typing import Any, Optional, Dict
from .baseoption import BaseOption, valid_name
from ..error import ConfigError
from ..i18n import _
@ -55,7 +54,7 @@ class SymLinkOption(BaseOption):
self._name = name
self._opt = opt
self._leadership = None
opt._add_dependency(self)
opt._add_dependency(self, "symlink")
def __getattr__(
self,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -37,6 +37,7 @@ class URLOption(StrOption):
__slots__ = tuple()
path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
_type = "URL"
_t_type = _("URL")
def __init__(
self,
@ -54,15 +55,16 @@ class URLOption(StrOption):
**kwargs,
) -> None:
# pylint: disable=too-many-arguments,too-many-locals,redefined-builtin
extra = {
"_domainname": DomainnameOption(
extra = {}
extra["_domainname"] = DomainnameOption(
name,
doc,
allow_ip=allow_ip,
type=type,
allow_without_dot=allow_without_dot,
),
"_port": PortOption(
_extra=extra,
)
extra["_port"] = PortOption(
name,
doc,
allow_range=allow_range,
@ -70,8 +72,8 @@ class URLOption(StrOption):
allow_wellknown=allow_wellknown,
allow_registred=allow_registred,
allow_private=allow_private,
),
}
_extra=extra,
)
super().__init__(
name,
doc,
@ -110,10 +112,18 @@ class URLOption(StrOption):
domain, port, files = self._get_domain_port_files(value)
# validate port
portoption = self.impl_get_extra("_port")
try:
portoption.validate(port)
except ValueError as err:
msg = _('the port "{0}" is invalid: {1}').format(domain, err)
raise ValueError(msg) from err
# validate domainname
domainnameoption = self.impl_get_extra("_domainname")
try:
domainnameoption.validate(domain)
except ValueError as err:
msg = _('the domain "{0}" is invalid: {1}').format(domain, err)
raise ValueError(msg) from err
# validate files
if files is not None and files != "" and not self.path_re.search(files):
raise ValueError(_("must ends with a valid resource name"))

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2017-2026 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
@ -33,6 +33,7 @@ class UsernameOption(RegexpOption):
# regexp build with 'man 8 adduser' informations
_regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
_type = "unix username"
_t_type = _("unix username")
class GroupnameOption(UsernameOption):
@ -40,3 +41,4 @@ class GroupnameOption(UsernameOption):
__slots__ = tuple()
_type = "unix groupname"
_t_type = _("unix groupname")

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"sets the options of the configuration objects Config object itself"
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2026 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
@ -94,7 +94,7 @@ EXPIRATION_TIME = 5
# demoting_error_warning
# all value errors are convert to warning (ValueErrorWarning)
DEFAULT_PROPERTIES = frozenset(["cache", "validator", "warnings"])
SPECIAL_PROPERTIES = {"frozen", "mandatory", "empty", "force_store_value"}
SPECIAL_PROPERTIES = {"frozen", "mandatory", "empty", "force_store_value", "validator"}
# Config can be in two defaut mode:
#
@ -143,12 +143,36 @@ RW_REMOVE = frozenset(
)
PROPERTIES_MAKE_SENSE = (
"cache"
"demoting_error_warning",
"disabled",
"empty",
"everything_frozen",
"expire",
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"force_store_value",
"frozen",
"hidden",
"mandatory",
"notempty",
"notunique",
"novalidator",
"permissive",
"unique",
"validator",
"warnings",
)
FORBIDDEN_SET_PROPERTIES = frozenset(["force_store_value"])
FORBIDDEN_SET_PERMISSIVES = frozenset(
[
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"force_store_value",
"validator",
]
)
ALLOWED_LEADER_PROPERTIES = {
@ -158,6 +182,8 @@ ALLOWED_LEADER_PROPERTIES = {
"unique",
"force_store_value",
"mandatory",
"validator",
"novalidator",
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"frozen",
@ -473,7 +499,7 @@ class Settings:
and new_prop not in ALLOWED_LEADER_PROPERTIES
):
raise LeadershipError(
_('leader cannot have "{new_prop}" property')
subconfig, "leadership-wrong_property", prop=new_prop
)
props.add(new_prop)
props -= self.getpermissives(subconfig)
@ -561,19 +587,15 @@ class Settings:
not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES
if not_allowed_properties:
raise LeadershipError(
_('leader cannot have "{0}" property').format(
display_list(not_allowed_properties)
)
subconfig,
"leadership-wrong_property",
prop=display_list(not_allowed_properties),
)
if (
"force_default_on_freeze" in properties
or "force_metaconfig_on_freeze" in properties
) and "frozen" not in properties:
raise LeadershipError(
_(
'a leader ({0}) cannot have "force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
).format(opt.impl_get_display_name())
)
raise LeadershipError(subconfig, "leadership-force_default_on_freeze")
self._properties.setdefault(subconfig.path, {})[subconfig.index] = properties
# values too because of follower values could have a PropertiesOptionError has value
subconfig.config_bag.context.reset_cache(subconfig)
@ -616,7 +638,8 @@ class Settings:
raise ConfigError(
_("cannot add those permissives: {0}").format(
" ".join(forbidden_permissives)
)
),
subconfig=subconfig,
)
self._permissives.setdefault(path, {})[index] = permissives
if subconfig is not None:

View file

@ -646,9 +646,9 @@ class TiramisuDict:
if self.remotable == "all" or childapi.has_dependency():
obj_form["remote"] = True
if childtype == "IPOption" and (
child.impl_get_extra("_private_only")
or not child.impl_get_extra("_allow_reserved")
or child.impl_get_extra("_cidr")
child.impl_get_extra("private_only")
or not child.impl_get_extra("allow_reserved")
or child.impl_get_extra("cidr")
):
obj_form["remote"] = True
if childtype == "DateOption":

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"takes care of the option's values and multi values"
# Copyright (C) 2013-2024 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2013-2026 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
@ -110,22 +110,64 @@ class Values:
value, owner = self._values.get(subconfig.path, {}).get(
subconfig.index, default_value
)
if owner == owners.default or (
"frozen" in subconfig.properties
self_properties = subconfig.properties or tuple()
if owner != owners.default and (
"frozen" in self_properties
and (
"force_default_on_freeze" in subconfig.properties
"force_default_on_freeze" in self_properties
or self.check_force_to_metaconfig(subconfig)
)
):
# the value is a default value
# get it
value = self.get_default_value(subconfig)
if owner == owners.default:
if(
"force_store_value" in subconfig.config_bag.properties
and "force_store_value" in self_properties
):
value = self.get_default_value(subconfig)
if value is not None:
owner = owners.forced
self._setvalue(
subconfig,
value,
owner,
)
else:
# the value is a default value
# get it
value = self.get_default_value(subconfig)
value, has_calculation = get_calculated_value(
subconfig,
value,
)
return value, has_calculation
def set_force_store_value(self, subconfig):
value = self.get_default_value(subconfig)
if value is None:
return None
owner = owners.forced
self._setvalue(
subconfig,
value,
owner,
)
return value, owner
def get_default_owner(
self,
subconfig: "SubConfig",
) -> Any:
msubconfig = self._get_modified_parent(subconfig)
if msubconfig is not None:
# retrieved value from parent config
return msubconfig.config_bag.context.get_values().getowner(
msubconfig
)
return owners.default
def get_default_value(
self,
subconfig: "SubConfig",
@ -189,10 +231,9 @@ class Values:
if subconfig.config_bag.context.impl_type == "config":
return True
# it's a not a config, force to metaconfig only in *explicitly* set
return "force_metaconfig_on_freeze" in settings.get_stored_properties(
return "force_metaconfig_on_freeze" in settings.get_personalize_properties(
subconfig.path,
subconfig.index,
frozenset(),
)
return False
@ -265,9 +306,10 @@ class Values:
) -> None:
"""set value to option"""
owner = self.get_context_owner()
self_properties = subconfig.properties
setting_properties = subconfig.config_bag.properties
ori_value = value
if "validator" in setting_properties:
if "validator" in setting_properties and "validator" in self_properties:
value, has_calculation = self.setvalue_validation(
subconfig,
value,
@ -287,14 +329,23 @@ class Values:
):
leader = subconfig.option.impl_get_leadership()
parent = subconfig.parent
parent._length = len(value)
try:
if isinstance(value, Calculation):
value, has_calculation = get_calculated_value(
subconfig,
value,
)
# parent._length = len(value)
leader.follower_force_store_value(
value,
parent,
owners.forced,
)
except:
pass
validator = (
"validator" in setting_properties
and "validator" in self_properties
and "demoting_error_warning" not in setting_properties
)
if validator and not has_calculation:
@ -304,7 +355,11 @@ class Values:
value,
validated=validator,
)
elif "validator" in setting_properties and has_calculation:
elif (
"validator" in setting_properties
and "validator" in self_properties
and has_calculation
):
cache = subconfig.config_bag.context.get_values_cache()
cache.delcache(subconfig.path)
@ -390,12 +445,15 @@ class Values:
woption(),
true_path=subconfig.path,
validate_properties=False,
check_dynamic_without_identifiers=False,
)
if not isinstance(options, list):
options = [options]
for option in options:
parent = option.parent
for identifier in identifier_values:
if identifier is None:
continue
name = option.option.impl_getname(identifier)
opt_subconfig = parent.get_child(
option.option,
@ -428,36 +486,28 @@ class Values:
If not found, return None
For follower option, return the Config where leader is modified
"""
def build_option_bag(subconfig, parent):
doption_bag = subconfig.copy()
config_bag = subconfig.config_bag.copy()
config_bag.context = parent
config_bag.unrestraint()
doption_bag.config_bag = config_bag
return doption_bag
for parent in subconfig.config_bag.context.get_parents():
doption_bag = build_option_bag(subconfig, parent)
parent_subconfig = subconfig.change_context(parent)
parent_subconfig.config_bag.unrestraint()
parent_subconfig.properties = subconfig.properties
if "force_metaconfig_on_freeze" in subconfig.properties:
# remove force_metaconfig_on_freeze only if option in metaconfig
# hasn't force_metaconfig_on_freeze properties
ori_properties = doption_bag.properties
settings = doption_bag.config_bag.context.get_settings()
doption_bag.properties = settings.getproperties(doption_bag)
if not self.check_force_to_metaconfig(doption_bag):
doption_bag.properties = ori_properties - {
ori_properties = parent_subconfig.properties
settings = parent_subconfig.config_bag.context.get_settings()
parent_subconfig.properties = settings.getproperties(parent_subconfig)
if not self.check_force_to_metaconfig(parent_subconfig):
parent_subconfig.properties = ori_properties - {
"force_metaconfig_on_freeze"
}
else:
doption_bag.properties = ori_properties
parent_subconfig.properties = ori_properties
parent_owner = parent.get_values().getowner(
doption_bag,
parent,
parent_subconfig,
only_default=True,
)
if parent_owner != owners.default:
return doption_bag
return parent_subconfig
return None
@ -511,14 +561,18 @@ class Values:
was present
:returns: a `setting.owners.Owner` object
"""
# context = subconfig.config_bag.context
# settings = context.get_settings()
# settings.validate_properties(subconfig)
self_properties = subconfig.properties
if (
"frozen" in subconfig.properties
and "force_default_on_freeze" in subconfig.properties
"frozen" in self_properties
and "force_default_on_freeze" in self_properties
):
return owners.default
setting_properties = subconfig.config_bag.properties
if (
"force_store_value" in setting_properties
and "force_store_value" in self_properties
):
self.set_force_store_value(subconfig)
if only_default:
if self.hasvalue(
subconfig.path,
@ -534,19 +588,18 @@ class Values:
)[1]
if validate_meta is not False and (
owner is owners.default
or "frozen" in subconfig.properties
and "force_metaconfig_on_freeze" in subconfig.properties
or "frozen" in self_properties
and "force_metaconfig_on_freeze" in self_properties
):
msubconfig = self._get_modified_parent(subconfig)
if msubconfig is not None:
values = msubconfig.config_bag.context.get_values()
owner = values.getowner(
msubconfig,
parent,
only_default=only_default,
)
elif "force_metaconfig_on_freeze" in subconfig.properties:
return owners.default
elif "force_metaconfig_on_freeze" in self_properties:
owner = owners.default
return owner
def set_owner(
@ -570,7 +623,8 @@ class Values:
raise ConfigError(
_(
'"{0}" is a default value, so we cannot change owner to "{1}"'
).format(subconfig.path, owner)
).format(subconfig.path, owner),
subconfig=subconfig,
)
subconfig.config_bag.context.get_settings().validate_frozen(subconfig)
self._values[subconfig.path][subconfig.index][1] = owner
@ -587,10 +641,15 @@ class Values:
"""reset value for an option"""
config_bag = subconfig.config_bag
hasvalue = self.hasvalue(subconfig.path)
self_properties = subconfig.properties
context = config_bag.context
setting_properties = config_bag.properties
if validate:
if hasvalue and "validator" in setting_properties:
if (
validate
and hasvalue
and "validator" in setting_properties
and "validator" in self_properties
):
fake_context = context.gen_fake_context()
fake_config_bag = config_bag.copy()
fake_config_bag.remove_validation()
@ -609,21 +668,14 @@ class Values:
fake_subconfig,
value,
)
# if hasvalue:
opt = subconfig.option
if opt.impl_is_leader():
opt.impl_get_leadership().reset(subconfig.parent)
if (
"force_store_value" in setting_properties
and "force_store_value" in subconfig.properties
and "force_store_value" in self_properties
):
value = self.get_default_value(subconfig)
self._setvalue(
subconfig,
value,
owners.forced,
)
self.set_force_store_value(subconfig)
else:
value = None
if subconfig.path in self._values:
@ -662,10 +714,11 @@ class Values:
index=subconfig.index,
):
return
self_properties = subconfig.properties
config_bag = subconfig.config_bag
context = config_bag.context
setting_properties = config_bag.properties
if "validator" in setting_properties:
if "validator" in setting_properties and "validator" in self_properties:
fake_context = context.gen_fake_context()
fake_config_bag = config_bag.copy()
fake_config_bag.remove_validation()
@ -686,17 +739,11 @@ class Values:
)
if (
"force_store_value" in setting_properties
and "force_store_value" in subconfig.properties
and "force_store_value" in self_properties
):
value = self.get_default_value(
subconfig,
)
self._setvalue(
subconfig,
value,
owners.forced,
)
force_store_value = self.set_force_store_value(subconfig)
if force_store_value:
value, owner = force_store_value
else:
self.resetvalue_index(subconfig)
context.reset_cache(subconfig)
@ -738,8 +785,8 @@ class Values:
if index >= length:
raise IndexError(
_(
"index {index} is greater than the length {length} "
"for option {subconfig.option.impl_get_display_name(with_quote=True)}"
f"index {index} is greater than the length {length} "
f"for option {subconfig.option.impl_get_display_name(subconfig, with_quote=True)}"
)
)
current_value.pop(index)