reorganize

This commit is contained in:
egarette@silique.fr 2023-05-11 15:44:48 +02:00
parent 30cd543a21
commit 6805cecfd5
50 changed files with 2517 additions and 1979 deletions

View file

@ -1179,7 +1179,11 @@ def autocheck_permissive(cfg, mcfg, pathread, pathwrite, confread, confwrite, **
cfg2_ = cfg.config(confread).unrestraint cfg2_ = cfg.config(confread).unrestraint
else: else:
cfg2_ = cfg.unrestraint cfg2_ = cfg.unrestraint
assert cfg_.option(pathread).permissive.get() == frozenset()
if not cfg_.option(pathread).option.isfollower():
assert cfg_.option(pathread).permissive.get() == frozenset()
else:
assert cfg_.option(pathread, 0).permissive.get() == frozenset()
if kwargs.get('permissive_od', False): if kwargs.get('permissive_od', False):
assert cfg_.option(pathread.rsplit('.', 1)[0]).permissive.get() == frozenset() assert cfg_.option(pathread.rsplit('.', 1)[0]).permissive.get() == frozenset()
@ -1200,7 +1204,10 @@ def autocheck_permissive(cfg, mcfg, pathread, pathwrite, confread, confwrite, **
cfg_.option(call_path).permissive.set(frozenset(['disabled'])) cfg_.option(call_path).permissive.set(frozenset(['disabled']))
# have permissive? # have permissive?
assert cfg_.option(pathread).permissive.get() == frozenset(['disabled']) if not cfg_.option(pathread).option.isfollower():
assert cfg_.option(pathread).permissive.get() == frozenset(['disabled'])
else:
assert cfg_.option(pathread, 0).permissive.get() == frozenset(['disabled'])
#if confwrite != confread: #if confwrite != confread:
# assert cfg.config(confread).unrestraint.option(pathread).permissive.get() == frozenset(['disabled']) # assert cfg.config(confread).unrestraint.option(pathread).permissive.get() == frozenset(['disabled'])

View file

@ -152,6 +152,16 @@ def test_information_config():
# assert not list_sessions() # assert not list_sessions()
def test_information_config_list():
od1 = make_description()
cfg = Config(od1)
string = 'some informations'
cfg.information.set('info', string)
#
assert cfg.information.exportation() == {None: {'info': string}}
assert set(cfg.information.list()) == {'info', 'doc'}
def test_information_exportation(): def test_information_exportation():
od1 = make_description() od1 = make_description()
cfg = Config(od1) cfg = Config(od1)
@ -199,6 +209,18 @@ def test_information_option():
# assert not list_sessions() # assert not list_sessions()
def test_information_option_2():
i1 = IntOption('test1', '')
i1.impl_set_information('info', 'value')
# it's a dict
assert set(i1.impl_list_information()) == {'info', 'doc'}
od1 = OptionDescription('test', '', [i1])
cfg = Config(od1)
# it's tuples
assert set(cfg.option('test1').information.list()) == {'info', 'doc'}
# assert not list_sessions()
def test_information_optiondescription(): def test_information_optiondescription():
od1 = make_description() od1 = make_description()
cfg = Config(od1) cfg = Config(od1)
@ -390,8 +412,7 @@ def test_config_od_name(config_type):
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
assert cfg.option('val.i').option.name() == 'i' assert cfg.option('val.i').option.name() == 'i'
assert cfg.option('val.s').option.name() == 's' assert cfg.option('val.s').option.name() == 's'
assert cfg.option('val.s').option.name(follow_symlink=True) == 'i' assert cfg.option('val.s').option.type() == _('integer')
assert cfg.option('val.s').option.type() == 'integer'
assert cfg.option('val').option.type() == 'optiondescription' assert cfg.option('val').option.type() == 'optiondescription'
# assert not list_sessions() # assert not list_sessions()
@ -403,7 +424,7 @@ def test_config_od_type(config_type):
cfg = Config(o2) cfg = Config(o2)
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
assert cfg.option('val').option.type() == 'optiondescription' assert cfg.option('val').option.type() == 'optiondescription'
assert cfg.option('val.i').option.type() == 'integer' assert cfg.option('val.i').option.type() == _('integer')
# assert not list_sessions() # assert not list_sessions()

View file

@ -1649,7 +1649,6 @@ def test_invalid_subdynod_dyndescription():
def test_invalid_symlink_dyndescription(): def test_invalid_symlink_dyndescription():
st = StrOption('st', '') st = StrOption('st', '')
st2 = SymLinkOption('st2', st) st2 = SymLinkOption('st2', st)
st2
with pytest.raises(ConfigError): with pytest.raises(ConfigError):
DynOptionDescription('dod', '', [st, st2], suffixes=Calculation(return_list)) DynOptionDescription('dod', '', [st, st2], suffixes=Calculation(return_list))
# assert not list_sessions() # assert not list_sessions()
@ -1658,7 +1657,6 @@ def test_invalid_symlink_dyndescription():
def test_nocallback_dyndescription(): def test_nocallback_dyndescription():
st = StrOption('st', '') st = StrOption('st', '')
st2 = StrOption('st2', '') st2 = StrOption('st2', '')
st, st2
with pytest.raises(TypeError): with pytest.raises(TypeError):
DynOptionDescription('dod', '', [st, st2]) DynOptionDescription('dod', '', [st, st2])
# assert not list_sessions() # assert not list_sessions()
@ -1843,10 +1841,9 @@ def test_dyn_leadership_requires():
def test_dyn_leadership_mandatory(): def test_dyn_leadership_mandatory():
nsd_zones_all = StrOption(name="nsd_zones_all", doc="nsd_zones_all", multi=True, default=['val1', 'val2']) nsd_zones_all = StrOption(name="nsd_zones_all", doc="nsd_zones_all", multi=True, default=['val1', 'val2'])
is_auto = BoolOption(name="is_auto_", doc="is auto") is_auto = BoolOption(name="is_auto_", doc="is auto")
# hostname = DomainnameOption(name="hostname_", multi=True, type='hostname', properties=frozenset({Calculation(func.calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(is_auto, todict=True, notraisepropertyerror=True), 'expected': ParamValue(True)})), Calculation(func.calc_value, Params(ParamValue('force_default_on_freeze'), kwargs={'condition': ParamOption(is_auto, todict=True, notraisepropertyerror=True), 'expected': ParamValue(True)}))}))
hostname = DomainnameOption(name="hostname_", doc="hostname_", multi=True, type='hostname') hostname = DomainnameOption(name="hostname_", doc="hostname_", multi=True, type='hostname')
choice = ChoiceOption(name="type_", doc="type_", values=('A', 'CNAME'), multi=True, default_multi="A") choice = ChoiceOption(name="type_", doc="type_", values=('A', 'CNAME'), multi=True, default_multi="A")
leadership = Leadership(name="hostname_", doc="hostname_", children=[hostname, choice], properties=frozenset({Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(is_auto, todict=True, notraisepropertyerror=True), 'expected': ParamValue(True)}))})) leadership = Leadership(name="hostname_", doc="hostname_", children=[hostname, choice], properties=frozenset({Calculation(calc_value, Params(ParamValue('hidden'), kwargs={'condition': ParamOption(is_auto, notraisepropertyerror=True), 'expected': ParamValue(True)}))}))
dyn = DynOptionDescription(name="nsd_zone_", doc="Zone ", suffixes=Calculation(calc_value, Params((ParamOption(nsd_zones_all, notraisepropertyerror=True)))), children=[is_auto, leadership], properties=frozenset({"normal"})) dyn = DynOptionDescription(name="nsd_zone_", doc="Zone ", suffixes=Calculation(calc_value, Params((ParamOption(nsd_zones_all, notraisepropertyerror=True)))), children=[is_auto, leadership], properties=frozenset({"normal"}))
od1 = OptionDescription(name="nsd", doc="nsd", children=[nsd_zones_all, dyn]) od1 = OptionDescription(name="nsd", doc="nsd", children=[nsd_zones_all, dyn])
cfg = Config(od1) cfg = Config(od1)

View file

@ -6,11 +6,11 @@ import pytest
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, IPOption, NetworkOption, NetmaskOption, \ from tiramisu import ChoiceOption, BoolOption, IntOption, IPOption, NetworkOption, NetmaskOption, \
StrOption, OptionDescription, Leadership, Config StrOption, OptionDescription, Leadership, Config, Calculation, ParamValue, calc_value, Params
from tiramisu.error import LeadershipError, PropertiesOptionError, ConfigError from tiramisu.error import LeadershipError, PropertiesOptionError, ConfigError
groups.family = groups.GroupType('family') groups.addgroup('family')
def compare(calculated, expected): def compare(calculated, expected):
@ -94,10 +94,12 @@ def test_iter_on_groups():
#test StopIteration #test StopIteration
break break
result = cfg.option('creole').list('option', result = cfg.option('creole').list('option',
group_type=groups.family) group_type=groups.family,
)
assert list(result) == [] assert list(result) == []
result = cfg.option('creole.general').list('optiondescription', result = cfg.option('creole.general').list('optiondescription',
group_type=groups.family) group_type=groups.family,
)
assert list(result) == [] assert list(result) == []
# assert not list_sessions() # assert not list_sessions()
@ -218,6 +220,61 @@ def test_groups_is_leader(config_type):
# assert not list_sessions() # assert not list_sessions()
def test_leader_list(config_type):
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, default_multi='value')
interface1 = Leadership('leadership', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('od', '', [interface1])
cfg = Config(od1)
cfg = get_config(cfg, config_type)
ret = cfg.option.list('all')
assert len(ret) == 1
assert ret[0].option.name() == 'leadership'
#
ret = cfg.option('leadership').list('all')
assert len(ret) == 1
assert ret[0].option.name() == 'ip_admin_eth0'
#
cfg.option('leadership.ip_admin_eth0').value.set(['a', 'b'])
cfg.option('leadership.netmask_admin_eth0', 0).value.set('c')
cfg.option('leadership.netmask_admin_eth0', 1).value.set('d')
ret = cfg.option('leadership').list('all')
assert ret[0].option.name() == 'ip_admin_eth0'
assert ret[1].option.name() == 'netmask_admin_eth0'
# assert ret[1].option.index() == 0
# assert ret[2].option.name() == 'netmask_admin_eth0'
# assert ret[2].option.index() == 1
# assert len(ret) == 3
# if config_type == 'tiramisu-api':
# cfg.send()
## assert not list_sessions()
def test_groups_is_multi_with_index(config_type):
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", ['val'], multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, default_multi='value')
interface1 = Leadership('leadership', '', [ip_admin_eth0, netmask_admin_eth0])
var = StrOption('var', "ip réseau autorisé", multi=True)
od2 = OptionDescription('od2', '', [var])
od1 = OptionDescription('od', '', [interface1, od2])
cfg = Config(od1)
cfg = get_config(cfg, config_type)
assert cfg.option('leadership.netmask_admin_eth0', 0).option.ismulti()
def test_groups_is_information_with_index(config_type):
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", ['val'], multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, default_multi='value')
interface1 = Leadership('leadership', '', [ip_admin_eth0, netmask_admin_eth0])
var = StrOption('var', "ip réseau autorisé", multi=True)
od2 = OptionDescription('od2', '', [var])
od1 = OptionDescription('od', '', [interface1, od2])
cfg = Config(od1)
cfg = get_config(cfg, config_type)
with pytest.raises(ConfigError):
assert cfg.option('leadership.netmask_admin_eth0', 0).information.set('key', 'value')
def test_groups_with_leader_in_root(): def test_groups_with_leader_in_root():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
@ -436,6 +493,9 @@ def test_groups_with_leader_index_mandatory(config_type):
cfg.property.read_write() cfg.property.read_write()
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.1']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.1'])
# index not allowed for leader
with pytest.raises(ConfigError):
cfg.option('ip_admin_eth0.ip_admin_eth0', 0).value.get()
# index is mandatory # index is mandatory
with pytest.raises(ConfigError): with pytest.raises(ConfigError):
cfg.option('ip_admin_eth0.netmask_admin_eth0').value.reset() cfg.option('ip_admin_eth0.netmask_admin_eth0').value.reset()
@ -907,7 +967,7 @@ def test_wrong_index():
assert cfg.option('od.ip_admin_eth0.ip_admin_eth0').option.get() assert cfg.option('od.ip_admin_eth0.ip_admin_eth0').option.get()
with pytest.raises(ConfigError): with pytest.raises(ConfigError):
cfg.option('od.ip_admin_eth0.ip_admin_eth0', 0).option.get() cfg.option('od.ip_admin_eth0.ip_admin_eth0', 0).option.get()
assert cfg.option('od.ip_admin_eth0.netmask_admin_eth0', 0).option.get() assert cfg.option('od.ip_admin_eth0.netmask_admin_eth0').option.get()
assert cfg.option('od.ip_admin_eth0').option.get() assert cfg.option('od.ip_admin_eth0').option.get()
with pytest.raises(ConfigError): with pytest.raises(ConfigError):
cfg.option('od.ip_admin_eth0', 0).option.get() cfg.option('od.ip_admin_eth0', 0).option.get()
@ -1011,17 +1071,14 @@ def test_follower_properties():
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['1.1.1.1', '192.168.0.0']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['1.1.1.1', '192.168.0.0'])
cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty',)
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty',) cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty',)
cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',) cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',)
# #
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.add('newproperty') cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.add('newproperty')
cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty',)
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty') cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty')
cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',) cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty',)
# #
cfg.option('ip_admin_eth0.netmask_admin_eth0').property.add('newproperty1') cfg.option('ip_admin_eth0.netmask_admin_eth0').property.add('newproperty1')
cfg.option('ip_admin_eth0.netmask_admin_eth0').property.get() == ('aproperty', 'newproperty1')
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty', 'newproperty1') cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).property.get() == ('aproperty', 'newproperty', 'newproperty1')
cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty', 'newproperty1') cfg.option('ip_admin_eth0.netmask_admin_eth0', 1).property.get() == ('aproperty', 'newproperty1')
# assert not list_sessions() # assert not list_sessions()
@ -1036,3 +1093,24 @@ def test_api_get_leader(config_type):
option = cfg.option('ip_admin_eth0.netmask_admin_eth0').option.leader() option = cfg.option('ip_admin_eth0.netmask_admin_eth0').option.leader()
assert option.option.get() == ip_admin_eth0 assert option.option.get() == ip_admin_eth0
# assert not list_sessions() # assert not list_sessions()
def test_leader_forbidden_properties(config_type):
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('conf', '', [interface1])
cfg = Config(od1)
with pytest.raises(LeadershipError):
cfg.option('ip_admin_eth0.ip_admin_eth0').property.add('permissive')
def test_leader_forbidden_properties_callback(config_type):
calc_property = Calculation(calc_value, Params(ParamValue('permissive')))
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True, properties=(calc_property,))
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('conf', '', [interface1])
cfg = Config(od1)
with pytest.raises(LeadershipError):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()

View file

@ -835,10 +835,10 @@ def test_meta_properties_requires1():
opt2 = BoolOption('opt2', "") opt2 = BoolOption('opt2', "")
disabled_property = Calculation(calc_value, disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'), Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(opt1, todict=True), kwargs={'condition': ParamOption(opt1),
'expected': ParamValue(False)})) 'expected': ParamValue(False)}))
od2 = OptionDescription('od2', "", [opt2], properties=(disabled_property,)) od2 = OptionDescription('od2', "", [opt2], properties=(disabled_property,))
opt3 = BoolOption('opt3', '', validators=[Calculation(valid_not_equal, Params((ParamOption(opt2), ParamSelfOption())))]) opt3 = BoolOption('opt3', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(opt2))))])
od = OptionDescription('root', '', [opt1, od2, opt3]) od = OptionDescription('root', '', [opt1, od2, opt3])
conf1 = Config(od, name='conf1') conf1 = Config(od, name='conf1')
conf1.property.read_write() conf1.property.read_write()
@ -859,7 +859,7 @@ def test_meta_properties_requires_mandatory():
'expected': ParamValue('yes'), 'expected': ParamValue('yes'),
'default': ParamValue(None)})) 'default': ParamValue(None)}))
ip_eth0 = IPOption('ip_eth0', "ip", Calculation(return_condition, Params(kwargs={'val': ParamOption(ip_address), 'condition': ParamOption(eth0_method), 'expected': ParamValue('dhcp')})), properties=(mandatory_property,)) ip_eth0 = IPOption('ip_eth0', "ip", Calculation(return_condition, Params(kwargs={'val': ParamOption(ip_address), 'condition': ParamOption(eth0_method), 'expected': ParamValue('dhcp')})), properties=(mandatory_property,))
ip_gw = IPOption('ip_gw', 'gw', validators=[Calculation(valid_not_equal, Params((ParamOption(ip_eth0), ParamSelfOption())))]) ip_gw = IPOption('ip_gw', 'gw', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(ip_eth0))))])
od = OptionDescription('root', '', [ip_gw, probes, eth0_method, ip_address, ip_eth0]) od = OptionDescription('root', '', [ip_gw, probes, eth0_method, ip_address, ip_eth0])
conf1 = Config(od, name='conf1') conf1 = Config(od, name='conf1')
conf1.property.read_write() conf1.property.read_write()

View file

@ -13,13 +13,17 @@ from tiramisu.i18n import _
try: try:
groups.family groups.family
except: except:
groups.family = groups.GroupType('family') groups.addgroup('family')
def a_func(): def a_func():
return None return None
def display_name(*args):
return 'display_name'
def test_option_valid_name(): def test_option_valid_name():
IntOption('test', '') IntOption('test', '')
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -76,6 +80,16 @@ def test_option_unknown():
cfg.option('test').value.list() cfg.option('test').value.list()
def test_option_description():
description = "it's ok"
i = IntOption('test', description)
od = OptionDescription('od', 'od', [i])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
assert cfg.option('od').option.description() == 'od'
assert cfg.option('od.test').option.description() == description
def test_option_get_information_default(): def test_option_get_information_default():
description = "it's ok" description = "it's ok"
string = 'some informations' string = 'some informations'
@ -178,7 +192,7 @@ def test_unknown_option():
def test_optiondescription_list(): def test_optiondescription_list():
groups.notfamily1 = groups.GroupType('notfamily1') groups.addgroup('notfamily1')
i = IntOption('test', '') i = IntOption('test', '')
i2 = IntOption('test', '') i2 = IntOption('test', '')
od1 = OptionDescription('od', '', [i]) od1 = OptionDescription('od', '', [i])
@ -210,7 +224,7 @@ def test_optiondescription_list():
def test_optiondescription_group(): def test_optiondescription_group():
groups.notfamily = groups.GroupType('notfamily') groups.addgroup('notfamily')
i = IntOption('test', '') i = IntOption('test', '')
i2 = IntOption('test', '') i2 = IntOption('test', '')
od1 = OptionDescription('od', '', [i]) od1 = OptionDescription('od', '', [i])
@ -240,7 +254,7 @@ def test_optiondescription_group():
def test_optiondescription_group_redefined(): def test_optiondescription_group_redefined():
try: try:
groups.notfamily = groups.GroupType('notfamily') groups.addgroup('notfamily')
except: except:
pass pass
i = IntOption('test', '') i = IntOption('test', '')
@ -301,12 +315,6 @@ def test_intoption():
# assert not list_sessions() # assert not list_sessions()
def test_get_display_type():
i1 = IntOption('test1', 'description', min_number=3)
assert i1.get_display_type() == _('integer')
# assert not list_sessions()
def test_option_not_in_config(): def test_option_not_in_config():
i1 = IntOption('test1', 'description', min_number=3) i1 = IntOption('test1', 'description', min_number=3)
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
@ -322,3 +330,25 @@ def test_option_unknown_func():
cfg = Config(od) cfg = Config(od)
with pytest.raises(ConfigError): with pytest.raises(ConfigError):
cfg.option('test1').value.unknown() cfg.option('test1').value.unknown()
def test_option_with_index():
i1 = IntOption('test1', 'description', [4, 5], min_number=3, multi=True)
i2 = IntOption('test2', 'description', max_number=3)
i3 = IntOption('test3', 'description', min_number=3, max_number=6, warnings_only=True)
od = OptionDescription('od', '', [i1, i2, i3])
cfg = Config(od)
with pytest.raises(ConfigError):
cfg.option('test1', 0).value.get()
def test_option_display_name():
i1 = IntOption('test1', 'description', min_number=3)
i2 = IntOption('test2', 'description', max_number=3)
i3 = IntOption('test3', 'description', min_number=3, max_number=6, warnings_only=True)
od = OptionDescription('od', '', [i1, i2, i3])
cfg = Config(od,
display_name=display_name,
)
assert cfg.option('test1').option.name() == 'test1'
assert cfg.option('test1').option.doc() == 'display_name'

View file

@ -145,6 +145,30 @@ def test_hidden_if_in2(config_type):
# assert not list_sessions() # assert not list_sessions()
def test_hidden_if_in3(config_type):
intoption = IntOption('int', 'Test int option', default=0)
hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(intoption),
'expected': ParamValue(1),
'default_0': ParamValue(None)}))
stroption = StrOption('str', 'Test string option', default="abc", properties=(hidden_property,))
od1 = OptionDescription('constraints', '', [stroption, intoption])
cfg_ori = Config(od1)
cfg_ori.property.read_write()
cfg = get_config(cfg_ori, config_type)
assert not 'hidden' in cfg.option('str').property.get()
cfg.option('int').value.set(1)
with pytest.raises(PropertiesOptionError):
cfg.option('str').value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('str').value.set('uvw')
if config_type == 'tiramisu-api':
cfg.send()
assert 'hidden' in cfg_ori.unrestraint.option('str').property.get()
# assert not list_sessions()
def test_hidden_if_in_with_group(config_type): def test_hidden_if_in_with_group(config_type):
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False) gcdummy = BoolOption('dummy', 'dummy', default=False)
@ -268,7 +292,8 @@ def test_callback(config_type):
assert cfg.option('val1').value.get() == 'val' assert cfg.option('val1').value.get() == 'val'
cfg.option('val1').value.set('new-val') cfg.option('val1').value.set('new-val')
assert cfg.option('val1').value.get() == 'new-val' assert cfg.option('val1').value.get() == 'new-val'
assert cfg.option('val1').option.defaultmulti() == None with pytest.raises(ConfigError):
assert cfg.option('val1').option.defaultmulti() == None
cfg.option('val1').value.reset() cfg.option('val1').value.reset()
assert cfg.option('val1').value.get() == 'val' assert cfg.option('val1').value.get() == 'val'
# assert not list_sessions() # assert not list_sessions()
@ -1543,3 +1568,8 @@ def test_calc_dependencies(config_type):
assert dep[0].option.get() == val3 assert dep[0].option.get() == val3
# #
assert cfg.option('val3').option.dependencies() == [] assert cfg.option('val3').option.dependencies() == []
def test_callback__kwargs_wrong(config_type):
with pytest.raises(ValueError):
Params(kwargs='string')

View file

@ -60,23 +60,27 @@ def test_mod_read_only_write():
config2 = Config(od1) config2 = Config(od1)
assert config.property.getdefault() == {'cache', 'validator', 'warnings'} assert config.property.getdefault() == {'cache', 'validator', 'warnings'}
assert config.property.getdefault('read_only', 'append') == {'frozen', assert config.property.getdefault('read_only', 'append') == {'frozen',
'disabled', 'disabled',
'validator', 'validator',
'everything_frozen', 'everything_frozen',
'mandatory', 'mandatory',
'empty', 'empty',
'force_store_value'} 'force_store_value',
}
assert config.property.getdefault('read_only', 'remove') == {'permissive', assert config.property.getdefault('read_only', 'remove') == {'permissive',
'hidden'} 'hidden',
}
assert config.property.getdefault('read_write', 'append') == {'frozen', assert config.property.getdefault('read_write', 'append') == {'frozen',
'disabled', 'disabled',
'validator', 'validator',
'hidden', 'hidden',
'force_store_value'} 'force_store_value',
}
assert config.property.getdefault('read_write', 'remove') == {'permissive', assert config.property.getdefault('read_write', 'remove') == {'permissive',
'everything_frozen', 'everything_frozen',
'mandatory', 'mandatory',
'empty'} 'empty',
}
# #
config.property.setdefault(frozenset(['cache'])) config.property.setdefault(frozenset(['cache']))
config.property.setdefault(type='read_only', when='append', properties=frozenset(['disabled'])) config.property.setdefault(type='read_only', when='append', properties=frozenset(['disabled']))
@ -595,6 +599,18 @@ def test_append_properties_force_store_value():
# assert not list_sessions() # assert not list_sessions()
def test_properties_get_add_reset():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.property.get() == {'validator', 'warnings', 'cache'}
cfg.property.add('frozen')
assert cfg.property.get() == {'validator', 'warnings', 'cache', 'frozen'}
cfg.property.reset()
assert cfg.property.get() == {'validator', 'warnings', 'cache'}
def test_reset_properties_force_store_value(): def test_reset_properties_force_store_value():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',)) gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy]) gcgroup = OptionDescription('gc', '', [gcdummy])
@ -683,7 +699,7 @@ def test_pprint():
intoption = IntOption('int', 'Test int option', default=0) intoption = IntOption('int', 'Test int option', default=0)
hidden_property = Calculation(calc_value, hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'), Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(intoption, todict=True), kwargs={'condition': ParamOption(intoption),
'expected_0': ParamValue(2), 'expected_0': ParamValue(2),
'expected_1': ParamValue(3), 'expected_1': ParamValue(3),
'expected_2': ParamValue(4), 'expected_2': ParamValue(4),
@ -691,35 +707,28 @@ def test_pprint():
calc_value_property_help) calc_value_property_help)
disabled_property = Calculation(calc_value, disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'), Params(ParamValue('disabled'),
kwargs={'condition_0': ParamOption(intoption, todict=True), kwargs={'condition_0': ParamOption(intoption),
'expected_0': ParamValue(1), 'expected_0': ParamValue(1),
'condition_1': ParamOption(s2, todict=True), 'condition_1': ParamOption(s2),
'expected_1': ParamValue('string')}), 'expected_1': ParamValue('string')}),
calc_value_property_help) calc_value_property_help)
stroption = StrOption('str', 'Test string option', default="abc", properties=(hidden_property, disabled_property)) stroption = StrOption('str', 'Test string option', default="abc", properties=(hidden_property, disabled_property))
# requires=[{'option': intoption, 'expected': 2, 'action': 'hidden', 'inverse': True},
# {'option': intoption, 'expected': 3, 'action': 'hidden', 'inverse': True},
# {'option': intoption, 'expected': 4, 'action': 'hidden', 'inverse': True},
# {'option': intoption, 'expected': 1, 'action': 'disabled'},
# {'option': s2, 'expected': 'string', 'action': 'disabled'}])
val2 = StrOption('val2', "") val2 = StrOption('val2', "")
hidden_property = Calculation(calc_value, hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'), Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(intoption, todict=True), kwargs={'condition': ParamOption(intoption),
'expected': ParamValue(1)}), 'expected': ParamValue(1)}),
calc_value_property_help) calc_value_property_help)
descr2 = OptionDescription("options", "options", [val2], properties=(hidden_property,)) descr2 = OptionDescription("options", "options", [val2], properties=(hidden_property,))
#descr2 = OptionDescription("options", "", [val2], requires=[{'option': intoption, 'expected': 1, 'action': 'hidden'}])
hidden_property = Calculation(calc_value, hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'), Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(stroption, todict=True), kwargs={'condition': ParamOption(stroption),
'expected': ParamValue('2'), 'expected': ParamValue('2'),
'reverse_condition': ParamValue(True)}), 'reverse_condition': ParamValue(True)}),
calc_value_property_help) calc_value_property_help)
val3 = StrOption('val3', "", properties=(hidden_property,)) val3 = StrOption('val3', "", properties=(hidden_property,))
#val3 = StrOption('val3', "", requires=[{'option': stroption, 'expected': '2', 'action': 'hidden', 'inverse': True}])
od1 = OptionDescription("options", "root option", [s, s2, s3, intoption, stroption, descr2, val3]) od1 = OptionDescription("options", "root option", [s, s2, s3, intoption, stroption, descr2, val3])
cfg = Config(od1) cfg = Config(od1)
@ -775,3 +784,95 @@ def test_pprint():
assert str(err) == msg_error.format('option', 'string3', prop, '"hidden"') assert str(err) == msg_error.format('option', 'string3', prop, '"hidden"')
del err del err
# assert not list_sessions() # assert not list_sessions()
def test_pprint_not_todict():
msg_error = _("cannot access to {0} \"{1}\" because has {2} {3}")
msg_is_not = _('the value of "{0}" is not {1}')
msg_is = _('the value of "{0}" is {1}')
properties = _('properties')
prop = _('property')
s = StrOption("string", "", default=["string"], default_multi="string", multi=True, properties=('hidden', 'disabled'))
s2 = StrOption("string2", "", default="string")
s3 = StrOption("string3", "", default=["string"], default_multi="string", multi=True, properties=('hidden',))
intoption = IntOption('int', 'Test int option', default=0)
hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(intoption),
'expected_0': ParamValue(2),
'expected_1': ParamValue(3),
'expected_2': ParamValue(4),
'reverse_condition': ParamValue(True)}),
)
disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'),
kwargs={'condition_0': ParamOption(intoption),
'expected_0': ParamValue(1),
'condition_1': ParamOption(s2),
'expected_1': ParamValue('string')}),
)
stroption = StrOption('str', 'Test string option', default="abc", properties=(hidden_property, disabled_property))
val2 = StrOption('val2', "")
hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(intoption),
'expected': ParamValue(1)}),
)
descr2 = OptionDescription("options", "options", [val2], properties=(hidden_property,))
hidden_property = Calculation(calc_value,
Params(ParamValue('hidden'),
kwargs={'condition': ParamOption(stroption),
'expected': ParamValue('2'),
'reverse_condition': ParamValue(True)}),
)
val3 = StrOption('val3', "", properties=(hidden_property,))
od1 = OptionDescription("options", "root option", [s, s2, s3, intoption, stroption, descr2, val3])
cfg = Config(od1)
cfg.property.read_write()
cfg.option('int').value.set(1)
err = None
try:
cfg.option('str').value.get()
except PropertiesOptionError as error:
err = error
list_disabled = '"disabled"'
list_hidden = '"hidden"'
assert str(err) == _(msg_error.format('option', 'Test string option', properties, display_list([list_disabled, list_hidden], add_quote=False)))
del err
err = None
try:
cfg.option('options.val2').value.get()
except PropertiesOptionError as error:
err = error
assert str(err) == msg_error.format('optiondescription', 'options', prop, '"hidden"')
err = None
try:
cfg.option('string').value.get()
except Exception as error:
err = error
assert str(err) == msg_error.format('option', 'string', properties, display_list(['disabled', 'hidden'], add_quote=True))
del err
err = None
try:
cfg.option('string3').value.get()
except Exception as error:
err = error
assert str(err) == msg_error.format('option', 'string3', prop, '"hidden"')
del err
# assert not list_sessions()
def test_property_invalid_type():
with pytest.raises(ValueError):
s3 = StrOption("string3", "", default=["string"], default_multi="string", multi=True, properties=(1,))

View file

@ -6,7 +6,8 @@ import pytest
from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \ from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \
IntOption, OptionDescription, Leadership, Config, Params, ParamValue, ParamOption, \ IntOption, OptionDescription, Leadership, Config, Params, ParamValue, ParamOption, \
ParamSelfOption, ParamIndex, Calculation, valid_ip_netmask, valid_network_netmask, \ ParamSelfOption, ParamIndex, ParamInformation, ParamSelfInformation, ParamSelfOption, Calculation, \
valid_ip_netmask, valid_network_netmask, \
valid_in_network, valid_broadcast, valid_not_equal, undefined valid_in_network, valid_broadcast, valid_not_equal, undefined
from tiramisu.setting import groups from tiramisu.setting import groups
from tiramisu.error import ValueErrorWarning, ConfigError, PropertiesOptionError from tiramisu.error import ValueErrorWarning, ConfigError, PropertiesOptionError
@ -105,11 +106,13 @@ def test_validator(config_type):
assert len(w) == 1 assert len(w) == 1
assert str(w[0].message) == msg assert str(w[0].message) == msg
assert cfg.option('opt2').value.valid() is False assert cfg.option('opt2').value.valid() is False
#
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
cfg.option('opt2').value.get() cfg.option('opt2').value.get()
assert len(w) == 1 assert len(w) == 1
assert str(w[0].message) == msg assert str(w[0].message) == msg
assert cfg.option('opt2').value.valid() is False assert cfg.option('opt2').value.valid() is False
#
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
cfg.option('opt2').value.get() cfg.option('opt2').value.get()
assert len(w) == 1 assert len(w) == 1
@ -118,6 +121,13 @@ def test_validator(config_type):
# assert not list_sessions() # assert not list_sessions()
def test_validator_not_valid(config_type):
with pytest.raises(ValueError):
StrOption('not_a_list', '', validators=Calculation(return_true, Params(ParamSelfOption())), default='val')
with pytest.raises(ValueError):
StrOption('not_calculation', '', validators=[str])
def test_validator_params(config_type): def test_validator_params(config_type):
opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params((ParamSelfOption(), ParamValue('yes'))))], default='val') opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params((ParamSelfOption(), ParamValue('yes'))))], default='val')
opt2 = StrOption('opt2', '', validators=[Calculation(return_false, Params((ParamSelfOption(), ParamValue('yes'))))]) opt2 = StrOption('opt2', '', validators=[Calculation(return_false, Params((ParamSelfOption(), ParamValue('yes'))))])
@ -324,7 +334,14 @@ def test_validator_warning(config_type):
assert len(w) == 1 assert len(w) == 1
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == opt2 assert w[0].message.opt() == opt2
assert str(w[0].message) == msg_err.format('val', opt2._display_name, 'opt2') + ', ' + 'test error return_false' assert str(w[0].message) == msg_err.format('val', opt2.get_type(), 'opt2') + ', ' + 'test error return_false'
#
with warnings.catch_warnings(record=True) as w:
cfg.nowarnings.option('opt2').value.set('val')
assert len(w) == 0
with warnings.catch_warnings(record=True) as w:
cfg.option('opt2').value.set('val')
assert len(w) == 1
# #
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
cfg.option('opt3').value.set(['val']) cfg.option('opt3').value.set(['val'])
@ -335,7 +352,7 @@ def test_validator_warning(config_type):
assert len(w) == 1 assert len(w) == 1
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == opt3 assert w[0].message.opt() == opt3
assert str(w[0].message) == msg_err.format('val1', opt3._display_name, 'opt3') + ', ' + 'test error' assert str(w[0].message) == msg_err.format('val1', opt3.get_type(), 'opt3') + ', ' + 'test error'
# #
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -348,9 +365,9 @@ def test_validator_warning(config_type):
assert len(w) == 2 assert len(w) == 2
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == opt2 assert w[0].message.opt() == opt2
assert str(w[0].message) == msg_err.format('val', opt2._display_name, 'opt2') + ', ' + 'test error return_false' assert str(w[0].message) == msg_err.format('val', opt2.get_type(), 'opt2') + ', ' + 'test error return_false'
assert w[1].message.opt() == opt3 assert w[1].message.opt() == opt3
assert str(w[1].message) == msg_err.format('val1', opt3._display_name, 'opt3') + ', ' + 'test error' assert str(w[1].message) == msg_err.format('val1', opt3.get_type(), 'opt3') + ', ' + 'test error'
# assert not list_sessions() # assert not list_sessions()
@ -419,13 +436,13 @@ def test_validator_warning_leadership(config_type):
assert len(w) == 1 assert len(w) == 1
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == netmask_admin_eth0 assert w[0].message.opt() == netmask_admin_eth0
assert str(w[0].message) == msg_err.format('val1', netmask_admin_eth0._display_name, display_name_netmask) + ', test error' assert str(w[0].message) == msg_err.format('val1', netmask_admin_eth0.get_type(), display_name_netmask) + ', test error'
# #
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val'])
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == ip_admin_eth0 assert w[0].message.opt() == ip_admin_eth0
assert str(w[0].message) == msg_err.format('val', ip_admin_eth0._display_name, display_name_ip) + ', test error return_false' assert str(w[0].message) == msg_err.format('val', ip_admin_eth0.get_type(), display_name_ip) + ', test error return_false'
else: else:
assert len(w) == 2 assert len(w) == 2
# #
@ -433,7 +450,7 @@ def test_validator_warning_leadership(config_type):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val', 'val1', 'val1']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val', 'val1', 'val1'])
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == ip_admin_eth0 assert w[0].message.opt() == ip_admin_eth0
assert str(w[0].message) == msg_err.format('val', ip_admin_eth0._display_name, display_name_ip) + ', test error return_false' assert str(w[0].message) == msg_err.format('val', ip_admin_eth0.get_type(), display_name_ip) + ', test error return_false'
else: else:
assert len(w) == 3 assert len(w) == 3
# #
@ -441,7 +458,7 @@ def test_validator_warning_leadership(config_type):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val1', 'val', 'val1']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val1', 'val', 'val1'])
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == ip_admin_eth0 assert w[0].message.opt() == ip_admin_eth0
assert str(w[0].message) == msg_err.format('val', ip_admin_eth0._display_name, display_name_ip) + ', test error return_false' assert str(w[0].message) == msg_err.format('val', ip_admin_eth0.get_type(), display_name_ip) + ', test error return_false'
else: else:
assert len(w) == 3 assert len(w) == 3
# #
@ -450,7 +467,7 @@ def test_validator_warning_leadership(config_type):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val1', 'val1', 'val']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val1', 'val1', 'val'])
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
assert w[0].message.opt() == ip_admin_eth0 assert w[0].message.opt() == ip_admin_eth0
assert str(w[0].message) == msg_err.format('val', ip_admin_eth0._display_name, display_name_ip) + ', test error return_false' assert str(w[0].message) == msg_err.format('val', ip_admin_eth0.get_type(), display_name_ip) + ', test error return_false'
else: else:
assert len(w) == 3 assert len(w) == 3
# assert not list_sessions() # assert not list_sessions()
@ -494,11 +511,12 @@ def test_validator_dependencies():
def test_validator_ip_netmask(config_type): def test_validator_ip_netmask(config_type):
a = IPOption('a', '') a = IPOption('a', '')
b = NetmaskOption('b', '', validators=[Calculation(valid_ip_netmask, Params((ParamOption(a, todict=True), ParamSelfOption())))]) b = NetmaskOption('b', '', validators=[Calculation(valid_ip_netmask, Params((ParamOption(a), ParamSelfOption())))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg_ori = Config(od1) cfg_ori = Config(od1)
cfg = cfg_ori cfg = cfg_ori
cfg = get_config(cfg_ori, config_type) cfg = get_config(cfg_ori, config_type)
cfg.option('b').value.set('255.255.255.0')
cfg.option('a').value.set('192.168.1.1') cfg.option('a').value.set('192.168.1.1')
cfg.option('b').value.set('255.255.255.0') cfg.option('b').value.set('255.255.255.0')
cfg.option('a').value.set('192.168.1.2') cfg.option('a').value.set('192.168.1.2')
@ -528,7 +546,7 @@ def test_validator_ip_netmask(config_type):
def test_validator_network_netmask(config_type): def test_validator_network_netmask(config_type):
a = NetworkOption('a', '') a = NetworkOption('a', '')
b = NetmaskOption('b', '', validators=[Calculation(valid_network_netmask, Params((ParamOption(a, todict=True), ParamSelfOption())))]) b = NetmaskOption('b', '', validators=[Calculation(valid_network_netmask, Params((ParamOption(a), ParamSelfOption())))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg_ori = Config(od1) cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type) cfg = get_config(cfg_ori, config_type)
@ -557,8 +575,8 @@ def test_validator_network_netmask(config_type):
def test_validator_ip_in_network(config_type): def test_validator_ip_in_network(config_type):
a = NetworkOption('a', '') a = NetworkOption('a', '')
b = NetmaskOption('b', '') b = NetmaskOption('b', '')
c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))))]) c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a), ParamOption(b))))])
d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))), warnings_only=True)]) d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a), ParamOption(b))), warnings_only=True)])
od1 = OptionDescription('od', '', [a, b, c, d]) od1 = OptionDescription('od', '', [a, b, c, d])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
cfg = Config(od1) cfg = Config(od1)
@ -581,8 +599,8 @@ def test_validator_ip_in_network(config_type):
def test_validator_ip_in_network_incomplete(config_type): def test_validator_ip_in_network_incomplete(config_type):
a = NetworkOption('a', '') a = NetworkOption('a', '')
b = NetmaskOption('b', '') b = NetmaskOption('b', '')
c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))))]) c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a), ParamOption(b))))])
d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))), warnings_only=True)]) d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a), ParamOption(b))), warnings_only=True)])
od1 = OptionDescription('od', '', [a, b, c, d]) od1 = OptionDescription('od', '', [a, b, c, d])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
cfg = Config(od1) cfg = Config(od1)
@ -603,8 +621,8 @@ def test_validator_ip_in_network_incomplete(config_type):
def test_validator_ip_in_network_cidr(config_type): def test_validator_ip_in_network_cidr(config_type):
a = NetworkOption('a', '', cidr=True) a = NetworkOption('a', '', cidr=True)
c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a))))])
d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=True)]) d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a))), warnings_only=True)])
od1 = OptionDescription('od', '', [a, c, d]) od1 = OptionDescription('od', '', [a, c, d])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
cfg = Config(od1) cfg = Config(od1)
@ -625,8 +643,8 @@ def test_validator_ip_in_network_cidr(config_type):
def test_validator_ip_in_network_cidr_incomplete(config_type): def test_validator_ip_in_network_cidr_incomplete(config_type):
a = NetworkOption('a', '', cidr=True) a = NetworkOption('a', '', cidr=True)
c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a))))])
d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=True)]) d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a))), warnings_only=True)])
od1 = OptionDescription('od', '', [a, c, d]) od1 = OptionDescription('od', '', [a, c, d])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
cfg = Config(od1) cfg = Config(od1)
@ -643,7 +661,7 @@ def test_validator_ip_in_network_cidr_incomplete(config_type):
def test_validator_ip_netmask_multi(config_type): def test_validator_ip_netmask_multi(config_type):
a = IPOption('a', '', multi=True) a = IPOption('a', '', multi=True)
b = NetmaskOption('b', '', multi=True, validators=[Calculation(valid_ip_netmask, Params((ParamOption(a, todict=True), ParamSelfOption())))]) b = NetmaskOption('b', '', multi=True, validators=[Calculation(valid_ip_netmask, Params((ParamOption(a), ParamSelfOption())))])
od = Leadership('a', '', [a, b]) od = Leadership('a', '', [a, b])
od2 = OptionDescription('od2', '', [od]) od2 = OptionDescription('od2', '', [od])
cfg_ori = Config(od2) cfg_ori = Config(od2)
@ -886,6 +904,39 @@ def test_validator_broadcast(config_type):
# assert not list_sessions() # assert not list_sessions()
def test_validator_broadcast_todict(config_type):
a = NetworkOption('a', '', multi=True)
b = NetmaskOption('b', '', multi=True, validators=[Calculation(valid_network_netmask, Params((ParamOption(a), ParamSelfOption())))])
c = BroadcastOption('c', '', multi=True, validators=[Calculation(valid_broadcast, Params((ParamOption(a), ParamOption(b), ParamSelfOption())))])
od = Leadership('a', '', [a, b, c])
od2 = OptionDescription('od2', '', [od])
cfg = Config(od2)
cfg = get_config(cfg, config_type)
#first, test network_netmask
cfg.option('a.a').value.set(['192.168.1.128'])
with pytest.raises(ValueError):
cfg.option('a.a').value.set(['255.255.255.0'])
#
cfg.option('a.a').value.set(['192.168.1.0'])
cfg.option('a.b', 0).value.set('255.255.255.0')
cfg.option('a.c', 0).value.set('192.168.1.255')
cfg.option('a.a').value.set(['192.168.1.1'])
with pytest.raises(ValueError):
cfg.option('a.b', 0).value.get()
with pytest.raises(ValueError):
cfg.option('a.c', 0).value.get()
#
cfg.option('a.a').value.set(['192.168.1.0', '192.168.2.128'])
cfg.option('a.b', 0).value.set('255.255.255.0')
cfg.option('a.b', 1).value.set('255.255.255.128')
cfg.option('a.c', 0).value.set('192.168.1.255')
cfg.option('a.c', 1).value.set('192.168.2.255')
with pytest.raises(ValueError):
cfg.option('a.c', 1).value.set('192.168.2.128')
cfg.option('a.c', 1).value.set('192.168.2.255')
# assert not list_sessions()
def test_validator_broadcast_warnings(config_type): def test_validator_broadcast_warnings(config_type):
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
a = NetworkOption('a', '', properties=('mandatory', 'disabled')) a = NetworkOption('a', '', properties=('mandatory', 'disabled'))
@ -969,7 +1020,7 @@ def test_validator_has_dependency():
def test_validator_warnings_only_more_option(config_type): def test_validator_warnings_only_more_option(config_type):
a = IntOption('a', '') a = IntOption('a', '')
b = IntOption('b', '') b = IntOption('b', '')
d = IntOption('d', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))), warnings_only=True)]) d = IntOption('d', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a), ParamOption(b))), warnings_only=True)])
od1 = OptionDescription('od', '', [a, b, d]) od1 = OptionDescription('od', '', [a, b, d])
cfg = Config(od1) cfg = Config(od1)
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
@ -988,7 +1039,7 @@ def test_validator_warnings_only_more_option(config_type):
def test_validator_error_prefix(): def test_validator_error_prefix():
a = IntOption('a', '') a = IntOption('a', '')
b = IntOption('b', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) b = IntOption('b', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
cfg.option('a').value.set(1) cfg.option('a').value.set(1)
@ -1006,7 +1057,7 @@ def test_validator_error_prefix():
def test_validator_warnings_only_option(config_type): def test_validator_warnings_only_option(config_type):
a = IntOption('a', '') a = IntOption('a', '')
b = IntOption('b', '', warnings_only=True, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) b = IntOption('b', '', warnings_only=True, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg_ori = Config(od1) cfg_ori = Config(od1)
cfg = get_config(cfg_ori, config_type) cfg = get_config(cfg_ori, config_type)
@ -1086,7 +1137,7 @@ def test_validator_not_equal_leadership_default():
def test_validator_default_diff(): def test_validator_default_diff():
a = IntOption('a', '', 3) a = IntOption('a', '', 3)
b = IntOption('b', '', 1, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) b = IntOption('b', '', 1, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
# FIXME cfg = get_config(cfg, config_type) # FIXME cfg = get_config(cfg, config_type)
@ -1107,7 +1158,7 @@ def test_validator_default_diff():
def test_validator_permissive(config_type): def test_validator_permissive(config_type):
a = IntOption('a', '', 1, properties=('hidden',)) a = IntOption('a', '', 1, properties=('hidden',))
b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
@ -1121,7 +1172,7 @@ def test_validator_permissive(config_type):
def test_validator_disabled(config_type): def test_validator_disabled(config_type):
a = IntOption('a', '', 1, properties=('disabled',)) a = IntOption('a', '', 1, properties=('disabled',))
b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True, raisepropertyerror=True))))]) b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, raisepropertyerror=True))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
@ -1133,7 +1184,7 @@ def test_validator_disabled(config_type):
def test_consistency_disabled_transitive(config_type): def test_consistency_disabled_transitive(config_type):
a = IntOption('a', '', 1, properties=('disabled',)) a = IntOption('a', '', 1, properties=('disabled',))
b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True, notraisepropertyerror=True))))]) b = IntOption('b', '', 2, validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, notraisepropertyerror=True))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
@ -1145,7 +1196,7 @@ def test_consistency_disabled_transitive(config_type):
def test_consistency_double_warnings(config_type): def test_consistency_double_warnings(config_type):
a = IntOption('a', '', 1) a = IntOption('a', '', 1)
b = IntOption('b', '', 1) b = IntOption('b', '', 1)
c = IntOption('c', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=True), Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(b, todict=True))), warnings_only=True)]) c = IntOption('c', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))), warnings_only=True), Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(b))), warnings_only=True)])
od = OptionDescription('od', '', [a, b, c]) od = OptionDescription('od', '', [a, b, c])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
od1 = OptionDescription('od2', '', [od]) od1 = OptionDescription('od2', '', [od])
@ -1179,8 +1230,8 @@ def test_consistency_warnings_error(config_type):
a = IntOption('a', '', 1) a = IntOption('a', '', 1)
b = IntOption('b', '', 1) b = IntOption('b', '', 1)
c = IntOption('c', '', validators=[ c = IntOption('c', '', validators=[
Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=True), Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))), warnings_only=True),
Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(b, todict=True)))) Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(b))))
]) ])
od1 = OptionDescription('od', '', [a, b, c]) od1 = OptionDescription('od', '', [a, b, c])
warnings.simplefilter("always", ValueErrorWarning) warnings.simplefilter("always", ValueErrorWarning)
@ -1196,7 +1247,7 @@ def test_consistency_warnings_error(config_type):
def test_consistency_not_equal_has_dependency(): def test_consistency_not_equal_has_dependency():
a = IntOption('a', '') a = IntOption('a', '')
b = IntOption('b', '', ) b = IntOption('b', '', )
b = IntOption('b', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) b = IntOption('b', '', validators=[Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))))])
od1 = OptionDescription('od', '', [a, b]) od1 = OptionDescription('od', '', [a, b])
cfg = Config(od1) cfg = Config(od1)
assert cfg.option('a').option.has_dependency() is False assert cfg.option('a').option.has_dependency() is False
@ -1204,3 +1255,25 @@ def test_consistency_not_equal_has_dependency():
assert cfg.option('a').option.has_dependency(False) is True assert cfg.option('a').option.has_dependency(False) is True
assert cfg.option('b').option.has_dependency(False) is False assert cfg.option('b').option.has_dependency(False) is False
# assert not list_sessions() # assert not list_sessions()
def test_validator_information(config_type):
opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params((ParamSelfInformation('key'), ParamValue('yes'))))], default='val')
opt2 = StrOption('opt2', '', validators=[Calculation(return_true, Params((ParamInformation('key'), ParamValue('yes'))))], default='val')
od1 = OptionDescription('root', '', [opt1, opt2])
cfg = Config(od1)
with pytest.raises(ConfigError):
cfg.option('opt1').value.get()
cfg.option('opt1').information.set('key', 'val')
assert cfg.option('opt1').value.get() == 'val'
cfg.option('opt1').information.set('key', 'val1')
with pytest.raises(ValueError):
cfg.option('opt1').value.get()
#
with pytest.raises(ConfigError):
assert cfg.option('opt2').value.get()
cfg.information.set('key', 'val')
assert cfg.option('opt2').value.get() == 'val'
cfg.information.set('key', 'val1')
with pytest.raises(ValueError):
cfg.option('opt2').value.get()

View file

@ -56,7 +56,7 @@ def test_requires(config_type):
a = BoolOption('activate_service', '', True) a = BoolOption('activate_service', '', True)
disabled_property = Calculation(calc_value, disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'), Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(a, todict=True), kwargs={'condition': ParamOption(a),
'expected': ParamValue(False)})) 'expected': ParamValue(False)}))
b = IPOption('ip_address_service', '', b = IPOption('ip_address_service', '',
properties=(disabled_property,)) properties=(disabled_property,))
@ -81,7 +81,7 @@ def test_requires_inverse(config_type):
a = BoolOption('activate_service', '', True) a = BoolOption('activate_service', '', True)
disabled_property = Calculation(calc_value, disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'), Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(a, todict=True), kwargs={'condition': ParamOption(a),
'expected': ParamValue(False), 'expected': ParamValue(False),
'reverse_condition': ParamValue(True)})) 'reverse_condition': ParamValue(True)}))
b = IPOption('ip_address_service', '', properties=(disabled_property,)) b = IPOption('ip_address_service', '', properties=(disabled_property,))
@ -158,13 +158,13 @@ def test_requires_same_action(config_type):
activate_service = BoolOption('activate_service', '', True) activate_service = BoolOption('activate_service', '', True)
new_property = Calculation(calc_value, new_property = Calculation(calc_value,
Params(ParamValue('new'), Params(ParamValue('new'),
kwargs={'condition': ParamOption(activate_service, todict=True), kwargs={'condition': ParamOption(activate_service),
'expected': ParamValue(False)}), 'expected': ParamValue(False)}),
calc_value_property_help) calc_value_property_help)
activate_service_web = BoolOption('activate_service_web', '', True, properties=(new_property,)) activate_service_web = BoolOption('activate_service_web', '', True, properties=(new_property,))
disabled_property = Calculation(calc_value, disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'), Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(activate_service_web, notraisepropertyerror=True, todict=True), kwargs={'condition': ParamOption(activate_service_web, notraisepropertyerror=True),
'expected': ParamValue(False)}), 'expected': ParamValue(False)}),
calc_value_property_help) calc_value_property_help)
ip_address_service_web = IPOption('ip_address_service_web', '', properties=(disabled_property,)) ip_address_service_web = IPOption('ip_address_service_web', '', properties=(disabled_property,))

View file

@ -1,44 +0,0 @@
from .autopath import do_autopath
do_autopath()
from tiramisu import BoolOption, StrOption, SymLinkOption, OptionDescription, DynOptionDescription, \
Calculation, Params, ParamOption, ParamValue, calc_value, Config
from pickle import dumps
import pytest
import sys, warnings
def test_diff_opt():
b = BoolOption('b', '')
disabled_property = Calculation(calc_value,
Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(b),
'expected': ParamValue(True),
'reverse_condition': ParamValue(True)}))
u = StrOption('u', '', properties=(disabled_property,))
s = SymLinkOption('s', u)
o = OptionDescription('o', '', [b, u, s])
o1 = OptionDescription('o1', '', [o])
with pytest.raises(NotImplementedError):
dumps(o1)
def test_diff_information_config():
b = BoolOption('b', '')
b.impl_set_information('info', 'oh')
b.impl_set_information('info1', 'oh')
b.impl_set_information('info2', 'oh')
o = OptionDescription('o', '', [b])
od1 = OptionDescription('o1', '', [o])
cfg = Config(od1)
c = cfg._config_bag.context
with pytest.raises(NotImplementedError):
dumps(c)
# assert not list_sessions()
def test_only_optiondescription():
b = BoolOption('b', '')
b
with pytest.raises(NotImplementedError):
dumps(b)

View file

@ -8,7 +8,7 @@ import warnings
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
from tiramisu import StrOption, IntOption, OptionDescription, submulti, Leadership, Config, \ from tiramisu import StrOption, IntOption, OptionDescription, submulti, Leadership, Config, \
MetaConfig, undefined, Params, ParamOption, Calculation MetaConfig, undefined, Params, ParamOption, Calculation
from tiramisu.error import LeadershipError from tiramisu.error import LeadershipError, PropertiesOptionError
def return_val(val=None): def return_val(val=None):
@ -48,6 +48,51 @@ def test_submulti():
# assert not list_sessions() # assert not list_sessions()
def test_submulti_mandatory():
multi = StrOption('multi', '', multi=submulti, properties=('mandatory',))
od1 = OptionDescription('od', '', [multi])
cfg = Config(od1)
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
#
cfg.property.read_write()
cfg.option('multi').value.set([['val']])
cfg.property.read_only()
assert cfg.option('multi').value.get() == [['val']]
#
cfg.property.read_write()
cfg.option('multi').value.set([['val'], ['']])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
#
cfg.property.read_write()
cfg.option('multi').value.set([['val'], [None]])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
#
cfg.property.read_write()
cfg.option('multi').value.set([['val'], []])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
#
cfg.property.read_write()
cfg.option('multi').value.set([['val'], ['val1', '']])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
#
cfg.property.read_write()
cfg.option('multi').value.set([['val'], ['val1', '', 'val2']])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('multi').value.get()
# assert not list_sessions()
def test_submulti_default_multi_not_list(): def test_submulti_default_multi_not_list():
with pytest.raises(ValueError): with pytest.raises(ValueError):
StrOption('multi2', '', default_multi='yes', multi=submulti) StrOption('multi2', '', default_multi='yes', multi=submulti)
@ -235,6 +280,50 @@ def test_values_with_leader_and_followers_submulti():
# assert not list_sessions() # assert not list_sessions()
def test_values_with_leader_and_followers_submulti_mandatory():
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=submulti, properties=('mandatory',))
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
od1 = OptionDescription('toto', '', [interface1])
cfg = Config(od1)
cfg.property.read_only()
owner = cfg.owner.get()
assert interface1.impl_get_group_type() == groups.leadership
assert cfg.option('ip_admin_eth0.ip_admin_eth0').owner.get() == owners.default
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == []
#
cfg.property.read_write()
cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(["192.168.230.145"])
cfg.property.read_only()
assert cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() == ["192.168.230.145"]
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
#
cfg.property.read_write()
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0"])
cfg.property.read_only()
assert cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() == ["255.255.255.0"]
#
cfg.property.read_write()
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0", None])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
#
cfg.property.read_write()
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0", ''])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
#
cfg.property.read_write()
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0", '', "255.255.255.0"])
cfg.property.read_only()
with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()
# assert not list_sessions()
def test_values_with_leader_and_followers_submulti_default_multi(): def test_values_with_leader_and_followers_submulti_default_multi():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti, default_multi=['255.255.0.0', '0.0.0.0']) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti, default_multi=['255.255.0.0', '0.0.0.0'])

View file

@ -4,10 +4,11 @@ from .autopath import do_autopath
do_autopath() do_autopath()
from .config import config_type, get_config from .config import config_type, get_config
from tiramisu import BoolOption, StrOption, SymLinkOption, \ from tiramisu import BoolOption, StrOption, SymLinkOption, submulti, \
OptionDescription, Leadership, Config, Calculation, calc_value, Params, ParamOption, ParamValue OptionDescription, Leadership, Config, Calculation, calc_value, Params, ParamOption, ParamValue
from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
from tiramisu.i18n import _
def return_value(): def return_value():
@ -19,9 +20,14 @@ def test_symlink_option(config_type):
boolopt = BoolOption("b", "", default=False) boolopt = BoolOption("b", "", default=False)
linkopt = SymLinkOption("c", boolopt) linkopt = SymLinkOption("c", boolopt)
od1 = OptionDescription("opt", "", od1 = OptionDescription("opt", "",
[linkopt, OptionDescription("s1", "", [boolopt])]) [linkopt, OptionDescription("s1", "", [boolopt])],
)
cfg = Config(od1) cfg = Config(od1)
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
assert not cfg.option('s1.b').option.issymlinkoption()
assert cfg.option('c').option.issymlinkoption()
assert cfg.option('s1.b').option.type() == _('boolean')
assert cfg.option('c').option.type() == _('boolean')
assert cfg.option('s1.b').value.get() is False assert cfg.option('s1.b').value.get() is False
cfg.option("s1.b").value.set(True) cfg.option("s1.b").value.set(True)
cfg.option("s1.b").value.set(False) cfg.option("s1.b").value.set(False)
@ -36,6 +42,62 @@ def test_symlink_option(config_type):
# assert not list_sessions() # assert not list_sessions()
def test_symlink_default(config_type):
boolopt = BoolOption("b", "", default=False)
linkopt = SymLinkOption("c", boolopt)
od1 = OptionDescription("opt", "",
[linkopt, OptionDescription("s1", "", [boolopt])],
)
cfg = Config(od1)
cfg = get_config(cfg, config_type)
assert not cfg.option('s1.b').option.ismulti()
assert not cfg.option('c').option.ismulti()
assert not cfg.option('s1.b').option.issubmulti()
assert not cfg.option('c').option.issubmulti()
assert not cfg.option('s1.b').option.default()
assert not cfg.option('c').option.default()
assert not cfg.option('s1.b').value.default()
assert not cfg.option('c').value.default()
with pytest.raises(ConfigError):
assert not cfg.option('s1.b').option.defaultmulti()
with pytest.raises(ConfigError):
assert not cfg.option('c').option.defaultmulti()
cfg.option("s1.b").value.set(True)
assert not cfg.option('s1.b').option.default()
assert not cfg.option('c').option.default()
assert not cfg.option('s1.b').value.default()
assert not cfg.option('c').value.default()
## assert not list_sessions()
def test_symlink_default_multi(config_type):
boolopt = BoolOption("b", "", default=[False], default_multi=True, multi=True)
linkopt = SymLinkOption("c", boolopt)
od1 = OptionDescription("opt", "",
[linkopt, OptionDescription("s1", "", [boolopt])],
)
cfg = Config(od1)
cfg = get_config(cfg, config_type)
assert cfg.option('s1.b').option.ismulti()
assert cfg.option('c').option.ismulti()
assert not cfg.option('s1.b').option.issubmulti()
assert not cfg.option('c').option.issubmulti()
assert cfg.option('s1.b').option.default() == [False]
assert cfg.option('c').option.default() == [False]
assert cfg.option('s1.b').value.default() == [False]
assert cfg.option('c').value.default() == [False]
assert cfg.option('s1.b').option.defaultmulti()
assert cfg.option('c').option.defaultmulti()
cfg.option("s1.b").value.set([True])
assert cfg.option('s1.b').option.default() == [False]
assert cfg.option('c').option.default() == [False]
assert cfg.option('s1.b').value.default() == [False]
assert cfg.option('c').value.default() == [False]
assert cfg.option('s1.b').option.defaultmulti()
assert cfg.option('c').option.defaultmulti()
## assert not list_sessions()
def test_symlink_assign_option(config_type): def test_symlink_assign_option(config_type):
boolopt = BoolOption("b", "", default=False) boolopt = BoolOption("b", "", default=False)
linkopt = SymLinkOption("c", boolopt) linkopt = SymLinkOption("c", boolopt)
@ -66,14 +128,10 @@ def test_symlink_addproperties():
od1 = OptionDescription('opt', '', [boolopt, linkopt]) od1 = OptionDescription('opt', '', [boolopt, linkopt])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
with pytest.raises(TypeError): with pytest.raises(ConfigError):
cfg.option('c').property.add('new') cfg.option('c').property.add('new')
try: with pytest.raises(ConfigError):
cfg.option('c').property.reset() cfg.option('c').property.reset()
except AssertionError:
pass
else:
raise Exception('must raise')
# assert not list_sessions() # assert not list_sessions()
@ -94,14 +152,10 @@ def test_symlink_addpermissives():
od1 = OptionDescription('opt', '', [boolopt, linkopt]) od1 = OptionDescription('opt', '', [boolopt, linkopt])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
with pytest.raises(TypeError): with pytest.raises(ConfigError):
cfg.option('c').permissive.set(frozenset(['new'])) cfg.option('c').permissive.set(frozenset(['new']))
try: with pytest.raises(ConfigError):
cfg.option('c').permissive.reset() cfg.option('c').permissive.reset()
except AssertionError:
pass
else:
raise Exception('must raise')
# assert not list_sessions() # assert not list_sessions()
@ -340,3 +394,14 @@ def test_symlink_list(config_type):
list_opt.append(opt.option.path()) list_opt.append(opt.option.path())
assert list_opt == ['c', 's1.b'] assert list_opt == ['c', 's1.b']
# assert not list_sessions() # assert not list_sessions()
def test_submulti():
multi = StrOption('multi', '', multi=submulti)
multi2 = SymLinkOption('multi2', multi)
od1 = OptionDescription('od', '', [multi, multi2])
cfg = Config(od1)
assert cfg.option('multi').option.ismulti()
assert cfg.option('multi').option.issubmulti()
assert cfg.option('multi2').option.ismulti()
assert cfg.option('multi2').option.issubmulti()

View file

@ -14,20 +14,19 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
from inspect import ismethod, getdoc, signature from inspect import getdoc
from time import time from typing import List, Set, Any, Optional, Callable, Dict
from typing import List, Set, Any, Optional, Callable, Union, Dict
from warnings import catch_warnings, simplefilter from warnings import catch_warnings, simplefilter
from functools import wraps from functools import wraps
from copy import deepcopy from copy import deepcopy
from .error import ConfigError, LeadershipError, PropertiesOptionError, ValueErrorWarning from .error import ConfigError, LeadershipError, ValueErrorWarning
from .i18n import _ from .i18n import _
from .setting import ConfigBag, OptionBag, owners, groups, Undefined, undefined, \ from .setting import ConfigBag, OptionBag, owners, groups, undefined, \
FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, EXPIRATION_TIME FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES
from .config import KernelConfig, KernelGroupConfig, KernelMetaConfig, KernelMixConfig from .config import KernelConfig, KernelGroupConfig, KernelMetaConfig, KernelMixConfig
from .option import RegexpOption, OptionDescription from .option import RegexpOption, OptionDescription, ChoiceOption
from .todict import TiramisuDict from .todict import TiramisuDict
@ -42,13 +41,12 @@ class TiramisuHelp:
def display(doc=''): def display(doc=''):
if _display: # pragma: no cover if _display: # pragma: no cover
print(doc) print(doc)
options = []
all_modules = dir(self) all_modules = dir(self)
modules = [] modules = []
max_len = 0 max_len = 0
force = False force = False
for module_name in all_modules: for module_name in all_modules:
if module_name in ['forcepermissive', 'unrestraint']: if module_name in ['forcepermissive', 'unrestraint', 'nowarnings']:
force = True force = True
max_len = max(max_len, len('forcepermissive')) max_len = max(max_len, len('forcepermissive'))
elif module_name != 'help' and not module_name.startswith('_'): elif module_name != 'help' and not module_name.startswith('_'):
@ -60,8 +58,16 @@ class TiramisuHelp:
display() display()
if force: if force:
display(_('Settings:')) display(_('Settings:'))
display(self._tmpl_help.format('forcepermissive', _('Access to option without verifying permissive properties')).expandtabs(max_len + 10)) display(self._tmpl_help.format('forcepermissive',
display(self._tmpl_help.format('unrestraint', _('Access to option without property restriction')).expandtabs(max_len + 10)) _('Access to option without verifying permissive '
'properties'),
).expandtabs(max_len + 10))
display(self._tmpl_help.format('unrestraint',
_('Access to option without property restriction')
).expandtabs(max_len + 10))
display(self._tmpl_help.format('nowarnings',
_('Do not warnings during validation')
).expandtabs(max_len + 10))
display() display()
if isinstance(self, TiramisuDispatcherOption): if isinstance(self, TiramisuDispatcherOption):
doc = _(getdoc(self.__call__)) doc = _(getdoc(self.__call__))
@ -81,7 +87,6 @@ class TiramisuHelp:
class CommonTiramisu(TiramisuHelp): class CommonTiramisu(TiramisuHelp):
_allow_optiondescription = True
_validate_properties = True _validate_properties = True
def _get_options_bag(self) -> OptionBag: def _get_options_bag(self) -> OptionBag:
@ -98,14 +103,6 @@ class CommonTiramisu(TiramisuHelp):
return options_bag return options_bag
def load_option(func):
def wrapped(self, *args, **kwargs):
options_bag = self._get_options_bag()
return func(self, options_bag, *args, **kwargs)
wrapped.func = func
return wrapped
def option_type(typ): def option_type(typ):
if not isinstance(typ, list): if not isinstance(typ, list):
types = [typ] types = [typ]
@ -115,16 +112,17 @@ def option_type(typ):
def wrapper(func): def wrapper(func):
@wraps(func) @wraps(func)
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
config_bag = args[0]._config_bag self = args[0]
if args[0]._config_bag.context.impl_type == 'group' and 'group' in types: config_bag = self._config_bag
if self._config_bag.context.impl_type == 'group' and 'group' in types:
options_bag = [OptionBag(None, options_bag = [OptionBag(None,
None, None,
args[0]._config_bag, self._config_bag,
path=args[0]._path, path=self._path,
)] )]
kwargs['is_group'] = True kwargs['is_group'] = True
return func(args[0], options_bag, *args[1:], **kwargs) return func(self, options_bag, *args[1:], **kwargs)
options_bag = args[0]._get_options_bag() options_bag = self._get_options_bag()
option = options_bag[-1].option option = options_bag[-1].option
if option.impl_is_optiondescription() and 'optiondescription' in types or \ if option.impl_is_optiondescription() and 'optiondescription' in types or \
not option.impl_is_optiondescription() and ( not option.impl_is_optiondescription() and (
@ -133,24 +131,29 @@ def option_type(typ):
'option' in types or \ 'option' in types or \
option.impl_is_leader() and 'leader' in types or \ option.impl_is_leader() and 'leader' in types or \
option.impl_is_follower() and 'follower' in types or \ option.impl_is_follower() and 'follower' in types or \
option.get_type() == 'choice' and 'choice' in types)): isinstance(option, ChoiceOption) and 'choice' in types)):
if not option.impl_is_optiondescription() and not option.impl_is_symlinkoption() and option.impl_is_follower(): if not option.impl_is_optiondescription() and \
if 'with_index' in types and args[0]._index is not None: not option.impl_is_symlinkoption() and \
raise ConfigError(_(f'please do not specify index ({args[0].__class__.__name__}.{func.__name__})')) option.impl_is_follower():
if 'with_index' not in types and args[0]._index is None: if 'with_index' not in types and 'with_or_without_index' not in types and \
raise ConfigError(_(f'please specify index with a follower option ({args[0].__class__.__name__}.{func.__name__})')) self._index is not None:
elif args[0]._index is not None: msg = _('please do not specify index '
raise ConfigError(_(f'please specify an index only for follower option ({args[0].__class__.__name__}.{func.__name__})')) f'({self.__class__.__name__}.{func.__name__})')
return func(args[0], options_bag, *args[1:], **kwargs) raise ConfigError(_(msg))
raise ConfigError(_(f'please specify a valid sub function ({args[0].__class__.__name__}.{func.__name__})')) if 'with_index' in types and self._index is None:
msg = _('please specify index with a follower option '
f'({self.__class__.__name__}.{func.__name__})')
raise ConfigError(msg)
return func(self, options_bag, *args[1:], **kwargs)
msg = _('please specify a valid sub function '
f'({self.__class__.__name__}.{func.__name__})')
raise ConfigError(msg)
wrapped.func = func wrapped.func = func
return wrapped return wrapped
return wrapper return wrapper
class CommonTiramisuOption(CommonTiramisu): class CommonTiramisuOption(CommonTiramisu):
_allow_optiondescription = False
_follower_need_index = True
_validate_properties = False _validate_properties = False
def __init__(self, def __init__(self,
@ -167,60 +170,14 @@ class CommonTiramisuOption(CommonTiramisu):
class _TiramisuOptionWalk: class _TiramisuOptionWalk:
def _filter(self, def _list(self,
opt, root_option_bag,
subconfig, type,
config_bag,
):
option_bag = OptionBag(opt,
None,
config_bag,
)
def _walk(self,
option,
recursive,
type_,
group_type, group_type,
config_bag, recursive,
subconfig,
): ):
options = [] assert type in ('all', 'option', 'optiondescription'), \
for opt in option.get_children(config_bag): _('unknown list type {}').format(type)
try:
#FIXME trop compliqué devrait faire avec get_sub_option_bag ou get_children ne devrait pas lister les variables disables
subsubconfig = self._filter(opt,
subconfig,
config_bag,
)
except PropertiesOptionError:
continue
if opt.impl_is_optiondescription():
if recursive:
options.extend(self._walk(opt,
recursive,
type_,
group_type,
config_bag,
subsubconfig))
if type_ == 'option' or (type_ == 'optiondescription' and \
group_type and opt.impl_get_group_type() != group_type):
continue
elif type_ == 'optiondescription':
continue
options.append(TiramisuOption(opt.impl_getpath(),
None,
config_bag,
))
return options
def _list2(self,
root_option_bag,
type,
group_type,
recursive,
):
assert type in ('all', 'option', 'optiondescription'), _('unknown list type {}').format(type)
assert group_type is None or isinstance(group_type, groups.GroupType), \ assert group_type is None or isinstance(group_type, groups.GroupType), \
_("unknown group_type: {0}").format(group_type) _("unknown group_type: {0}").format(group_type)
options = [] options = []
@ -233,11 +190,18 @@ class _TiramisuOptionWalk:
group_type=group_type, group_type=group_type,
): ):
if isinstance(option_bag, dict): if isinstance(option_bag, dict):
for opt_bag in option_bag.values(): for opts_bag in option_bag.values():
options.append(TiramisuOption(opt_bag.path, if isinstance(opts_bag, OptionBag):
opt_bag.index, options.append(TiramisuOption(opts_bag.path,
self._config_bag, opts_bag.index,
)) self._config_bag,
))
else:
for opt_bag in opts_bag:
options.append(TiramisuOption(opt_bag.path,
opt_bag.index,
self._config_bag,
))
else: else:
options.append(TiramisuOption(option_bag.path, options.append(TiramisuOption(option_bag.path,
option_bag.index, option_bag.index,
@ -245,82 +209,48 @@ class _TiramisuOptionWalk:
)) ))
return options return options
def _list(self,
type,
group_type,
recursive,
root_option,
subconfig,
config_bag,
):
assert type in ('all', 'option', 'optiondescription'), _('unknown list type {}').format(type)
assert group_type is None or isinstance(group_type, groups.GroupType), \
_("unknown group_type: {0}").format(group_type)
if config_bag.properties and 'warnings' in config_bag.properties:
config_bag = config_bag.copy()
config_bag.remove_warnings()
options = []
for opt in self._walk(root_option,
recursive,
type,
group_type,
config_bag,
subconfig,
):
options.append(opt)
return options
class _TiramisuOptionOptionDescription(CommonTiramisuOption): class _TiramisuOptionOptionDescription(CommonTiramisuOption):
"""Manage option""" """Manage option"""
_allow_optiondescription = True
_follower_need_index = False
_validate_properties = False _validate_properties = False
@load_option @option_type(['optiondescription', 'option', 'with_or_without_index'])
def get(self, options_bag: List[OptionBag]): def get(self, options_bag: List[OptionBag]):
"""Get Tiramisu option""" """Get Tiramisu option"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option return option_bag.option
@load_option @option_type(['optiondescription'])
def isleadership(self, options_bag: List[OptionBag]): def isleadership(self, options_bag: List[OptionBag]):
"""Test if option is a leader or a follower""" """Test if option is a leader or a follower"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_is_leadership() return option_bag.option.impl_is_leadership()
@load_option @option_type(['optiondescription', 'option', 'with_or_without_index'])
def doc(self, options_bag: List[OptionBag]): def doc(self, options_bag: List[OptionBag]):
"""Get option document""" """Get option document"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_get_display_name() return option_bag.option.impl_get_display_name()
@load_option @option_type(['optiondescription', 'option', 'with_or_without_index'])
def description(self, options_bag: List[OptionBag]): def description(self, options_bag: List[OptionBag]):
"""Get option description""" """Get option description"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_get_information('doc', None) return option_bag.option.impl_get_information('doc', None)
@load_option @option_type(['optiondescription', 'option', 'symlink', 'with_or_without_index'])
def name(self, def name(self, options_bag: List[OptionBag]) -> str:
options_bag: List[OptionBag],
follow_symlink: bool=False,
) -> str:
"""Get option name""" """Get option name"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
if not follow_symlink or \ return option_bag.option.impl_getname()
option_bag.option.impl_is_optiondescription() or \
not option_bag.option.impl_is_symlinkoption():
return option_bag.option.impl_getname()
return option_bag.option.impl_getopt().impl_getname()
@load_option @option_type(['optiondescription', 'option', 'with_or_without_index', 'symlink'])
def path(self, options_bag: List[OptionBag]) -> str: def path(self, options_bag: List[OptionBag]) -> str:
"""Get option path""" """Get option path"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.path return option_bag.path
@load_option @option_type(['optiondescription', 'option', 'symlink'])
def has_dependency(self, def has_dependency(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
self_is_dep=True, self_is_dep=True,
@ -329,7 +259,7 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption):
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_has_dependency(self_is_dep) return option_bag.option.impl_has_dependency(self_is_dep)
@load_option @option_type(['optiondescription', 'option'])
def dependencies(self, options_bag: List[OptionBag]): def dependencies(self, options_bag: List[OptionBag]):
"""Get dependencies from this option""" """Get dependencies from this option"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -341,13 +271,13 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption):
)) ))
return options return options
@load_option @option_type(['optiondescription', 'option', 'with_or_without_index'])
def isoptiondescription(self, options_bag: List[OptionBag]): def isoptiondescription(self, options_bag: List[OptionBag]):
"""Test if option is an optiondescription""" """Test if option is an optiondescription"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_is_optiondescription() return option_bag.option.impl_is_optiondescription()
@load_option @option_type(['optiondescription', 'option', 'with_index'])
def properties(self, def properties(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
only_raises=False, only_raises=False,
@ -364,7 +294,6 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption):
return settings.getproperties(option_bag, return settings.getproperties(option_bag,
apply_requires=False, apply_requires=False,
) )
# do not check cache properties/permissives which are not save (unrestraint, ...)
return settings.calc_raises_properties(option_bag, return settings.calc_raises_properties(option_bag,
apply_requires=False, apply_requires=False,
uncalculated=uncalculated, uncalculated=uncalculated,
@ -383,55 +312,51 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption):
class TiramisuOptionOption(_TiramisuOptionOptionDescription): class TiramisuOptionOption(_TiramisuOptionOptionDescription):
"""Manage option""" """Manage option"""
@load_option @option_type(['option', 'symlink', 'with_or_without_index'])
def ismulti(self, options_bag: List[OptionBag]): def ismulti(self, options_bag: List[OptionBag]):
"""Test if option could have multi value""" """Test if option could have multi value"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_is_multi() return option_bag.option.impl_is_multi()
@load_option @option_type(['option', 'symlink', 'with_or_without_index'])
def issubmulti(self, options_bag: List[OptionBag]): def issubmulti(self, options_bag: List[OptionBag]):
"""Test if option could have submulti value""" """Test if option could have submulti value"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return option_bag.option.impl_is_submulti() return option_bag.option.impl_is_submulti()
@load_option @option_type(['option', 'with_or_without_index'])
def isleader(self, options_bag: List[OptionBag]): def isleader(self, options_bag: List[OptionBag]):
"""Test if option is a leader""" """Test if option is a leader"""
option_bag = options_bag[-1] return options_bag[-1].option.impl_is_leader()
return option_bag.option.impl_is_leader()
@load_option @option_type(['option', 'with_or_without_index'])
def isfollower(self, options_bag: List[OptionBag]): def isfollower(self, options_bag: List[OptionBag]):
"""Test if option is a follower""" """Test if option is a follower"""
option_bag = options_bag[-1] return options_bag[-1].option.impl_is_follower()
return option_bag.option.impl_is_follower()
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def isdynamic(self, options_bag: List[OptionBag]): def isdynamic(self, options_bag: List[OptionBag]):
"""Test if option is a dynamic optiondescription""" """Test if option is a dynamic optiondescription"""
option_bag = options_bag[-1] return options_bag[-1].option.impl_is_dynsymlinkoption()
return option_bag.option.impl_is_dynsymlinkoption()
@load_option @option_type(['option', 'symlink', 'with_or_without_index'])
def issymlinkoption(self, options_bag: List[OptionBag]) -> bool: def issymlinkoption(self, options_bag: List[OptionBag]) -> bool:
"""Test if option is a symlink option""" """Test if option is a symlink option"""
option_bag = options_bag[-1] return options_bag[-1].option.impl_is_symlinkoption()
return option_bag.option.impl_is_symlinkoption()
@load_option @option_type(['option', 'with_or_without_index', 'symlink'])
def default(self, options_bag: List[OptionBag]): def default(self, options_bag: List[OptionBag]):
"""Get default value for an option (not for optiondescription)""" """Get default value for an option (not for optiondescription)"""
option_bag = options_bag[-1] return options_bag[-1].option.impl_getdefault()
return option_bag.option.impl_getdefault()
@load_option @option_type(['option', 'with_or_without_index', 'symlink'])
def defaultmulti(self, options_bag: List[OptionBag]): def defaultmulti(self, options_bag: List[OptionBag]):
"""Get default value when added a value for a multi option (not for optiondescription)""" """Get default value when added a value for a multi option (not for optiondescription)"""
option_bag = options_bag[-1] if not options_bag[-1].option.impl_is_multi():
return option_bag.option.impl_getdefault_multi() raise ConfigError(_('only multi value has defaultmulti'))
return options_bag[-1].option.impl_getdefault_multi()
@load_option @option_type(['option', 'optiondescription', 'symlink', 'with_or_without_index'])
def type(self, options_bag: List[OptionBag]): def type(self, options_bag: List[OptionBag]):
"""Get de option type""" """Get de option type"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -439,7 +364,7 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription):
return 'optiondescription' return 'optiondescription'
return option_bag.option.get_type() return option_bag.option.get_type()
@load_option @option_type('option')
def pattern(self, options_bag: List[OptionBag]) -> str: def pattern(self, options_bag: List[OptionBag]) -> str:
"""Get the option pattern""" """Get the option pattern"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -456,15 +381,16 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription):
#FIXME only from 0.0.0.0 to 255.255.255.255 #FIXME only from 0.0.0.0 to 255.255.255.255
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]?)$' 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]?)$'
@load_option @option_type('option')
def leader(self, options_bag: List[OptionBag]): def leader(self, options_bag: List[OptionBag]):
"""Get the leader option for a follower option""" """Get the leader option for a follower option"""
option_bag = options_bag[-1] path = options_bag[-1].option.impl_get_leadership().get_leader().impl_getpath()
return TiramisuOption(option_bag.option.impl_get_leadership().get_leader().impl_getpath(), return TiramisuOption(path,
None, None,
self._config_bag) self._config_bag,
)
@load_option @option_type(['option', 'with_or_without_index'])
def index(self, options_bag: List[OptionBag]): def index(self, options_bag: List[OptionBag]):
"""Get then index of option""" """Get then index of option"""
return options_bag[-1].index return options_bag[-1].index
@ -474,7 +400,7 @@ class TiramisuOptionOwner(CommonTiramisuOption):
#FIXME optiondescription must not have Owner! #FIXME optiondescription must not have Owner!
"""Manage option's owner""" """Manage option's owner"""
@option_type(['symlink', 'option']) @option_type(['symlink', 'option', 'with_index'])
def get(self, options_bag: List[OptionBag]): def get(self, options_bag: List[OptionBag]):
"""Get owner for a specified option""" """Get owner for a specified option"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -482,17 +408,15 @@ class TiramisuOptionOwner(CommonTiramisuOption):
parent_option_bag = options_bag[-2] parent_option_bag = options_bag[-2]
else: else:
parent_option_bag = None parent_option_bag = None
return self._config_bag.context.get_owner(option_bag, return self._config_bag.context.get_owner(option_bag)
parent_option_bag,
)
@option_type(['symlink', 'option']) @option_type(['symlink', 'option', 'with_index'])
def isdefault(self, options_bag: List[OptionBag]): def isdefault(self, options_bag: List[OptionBag]):
"""Is option has defaut value""" """Is option has defaut value"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return self._config_bag.context.get_values().is_default_owner(option_bag) return self._config_bag.context.get_values().is_default_owner(option_bag)
@option_type('option') @option_type(['option', 'with_index'])
def set(self, def set(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
owner: str, owner: str,
@ -511,11 +435,9 @@ class TiramisuOptionOwner(CommonTiramisuOption):
class TiramisuOptionProperty(CommonTiramisuOption): class TiramisuOptionProperty(CommonTiramisuOption):
"""Manage option's property""" """Manage option's property"""
_allow_optiondescription = True
_follower_need_index = False
_validate_properties = False _validate_properties = False
@load_option @option_type(['option', 'optiondescription', 'with_index'])
def get(self, def get(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
only_raises=False, only_raises=False,
@ -525,14 +447,13 @@ class TiramisuOptionProperty(CommonTiramisuOption):
option_bag = options_bag[-1] option_bag = options_bag[-1]
if not only_raises: if not only_raises:
return option_bag.properties return option_bag.properties
# do not check cache properties/permissives which are not save (unrestraint, ...)
settings = self._config_bag.context.get_settings() settings = self._config_bag.context.get_settings()
ret = settings.calc_raises_properties(option_bag, ret = settings.calc_raises_properties(option_bag,
uncalculated=uncalculated, uncalculated=uncalculated,
) )
return ret return ret
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def add(self, def add(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
prop,): prop,):
@ -542,15 +463,15 @@ class TiramisuOptionProperty(CommonTiramisuOption):
raise ConfigError(_('cannot add this property: "{0}"').format( raise ConfigError(_('cannot add this property: "{0}"').format(
' '.join(prop))) ' '.join(prop)))
settings = self._config_bag.context.get_settings() settings = self._config_bag.context.get_settings()
props = settings._getproperties(option_bag.path, props = settings.get_stored_properties(option_bag.path,
option_bag.index, option_bag.index,
option_bag.option.impl_getproperties(), option_bag.option.impl_getproperties(),
) )
settings.setproperties(option_bag, settings.setproperties(option_bag,
props | {prop}, props | {prop},
) )
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def remove(self, def remove(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
prop, prop,
@ -562,7 +483,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
props - {prop}, props - {prop},
) )
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def reset(self, options_bag: List[OptionBag]): def reset(self, options_bag: List[OptionBag]):
"""Reset all personalised properties""" """Reset all personalised properties"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -571,16 +492,14 @@ class TiramisuOptionProperty(CommonTiramisuOption):
class TiramisuOptionPermissive(CommonTiramisuOption): class TiramisuOptionPermissive(CommonTiramisuOption):
"""Manage option's permissive""" """Manage option's permissive"""
_allow_optiondescription = True
_follower_need_index = False
@load_option @option_type(['option', 'optiondescription', 'symlink', 'with_index'])
def get(self, options_bag: List[OptionBag]): def get(self, options_bag: List[OptionBag]):
"""Get permissives value""" """Get permissives value"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
return self._config_bag.context.get_settings().getpermissives(option_bag) return self._config_bag.context.get_settings().getpermissives(option_bag)
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def set(self, def set(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
permissives, permissives,
@ -591,7 +510,7 @@ class TiramisuOptionPermissive(CommonTiramisuOption):
permissives=permissives, permissives=permissives,
) )
@load_option @option_type(['option', 'optiondescription', 'with_index'])
def reset(self, options_bag: List[OptionBag]): def reset(self, options_bag: List[OptionBag]):
"""Reset all personalised permissive""" """Reset all personalised permissive"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -600,40 +519,36 @@ class TiramisuOptionPermissive(CommonTiramisuOption):
class TiramisuOptionInformation(CommonTiramisuOption): class TiramisuOptionInformation(CommonTiramisuOption):
"""Manage option's informations""" """Manage option's informations"""
_allow_optiondescription = True
_follower_need_index = False
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def get(self, def get(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
key: str, name: str,
default=undefined, default=undefined,
) -> Any: ) -> Any:
"""Get information""" """Get information"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
try: try:
return self._config_bag.context.get_values().get_information(self._config_bag, return self._config_bag.context.get_values().get_information(option_bag,
option_bag, name,
key,
undefined, undefined,
) )
except ValueError: except ValueError:
return option_bag.option.impl_get_information(key, default) return option_bag.option.impl_get_information(name, default)
@load_option @option_type(['option', 'optiondescription'])
def set(self, def set(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
key: str, key: str,
value: Any) -> None: value: Any) -> None:
"""Set information""" """Set information"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
self._config_bag.context.get_values().set_information(self._config_bag, self._config_bag.context.get_values().set_information(option_bag,
option_bag,
key, key,
value, value,
) )
@load_option @option_type(['option', 'optiondescription'])
def reset(self, def reset(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
key: str, key: str,
@ -644,7 +559,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
path=option_bag.path, path=option_bag.path,
) )
@load_option @option_type(['option', 'optiondescription', 'with_or_without_index'])
def list(self, def list(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
) -> list: ) -> list:
@ -657,19 +572,14 @@ class TiramisuOptionInformation(CommonTiramisuOption):
class TiramisuOptionValue(CommonTiramisuOption): class TiramisuOptionValue(CommonTiramisuOption):
"""Manage option's value""" """Manage option's value"""
_allow_optiondescription = True
_follower_need_index = True
_validate_properties = True _validate_properties = True
@option_type('optiondescription') @option_type('optiondescription')
def dict(self, options_bag: List[OptionBag]): def dict(self, options_bag: List[OptionBag]):
"""Dict with path as key and value""" """Dict with path as key and value"""
#FIXME : .nowarnings comme .forcepermissive if not withwarning and self._config_bag.properties and 'warnings' in self._config_bag.properties:
# option_bag.config_bag = self._config_bag.copy()
# option_bag.config_bag.remove_warnings()
return self._config_bag.context.make_dict(options_bag[-1]) return self._config_bag.context.make_dict(options_bag[-1])
@option_type(['option', 'symlink']) @option_type(['option', 'symlink', 'with_index'])
def get(self, def get(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
): ):
@ -688,7 +598,7 @@ class TiramisuOptionValue(CommonTiramisuOption):
parent_option_bag, parent_option_bag,
) )
@option_type('option') @option_type(['option', 'with_index'])
def set(self, def set(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
value, value,
@ -701,17 +611,18 @@ class TiramisuOptionValue(CommonTiramisuOption):
idx = value.index(undefined) idx = value.index(undefined)
soption_bag = option_bag.copy() soption_bag = option_bag.copy()
soption_bag.index = idx soption_bag.index = idx
value[idx] = values.getdefaultvalue(soption_bag) value[idx] = values.get_default_value(soption_bag)
elif value == undefined: elif value == undefined:
value = values.getdefaultvalue(option_bag) value = values.get_default_value(option_bag)
if option_bag.option.impl_is_leader() and len(value) < self._config_bag.context.get_length_leadership(options_bag[-2]): if option_bag.option.impl_is_leader() and \
len(value) < self._config_bag.context.get_length_leadership(options_bag[-2]):
raise LeadershipError(_('cannot reduce length of the leader "{}"' raise LeadershipError(_('cannot reduce length of the leader "{}"'
'').format(option_bag.option.impl_get_display_name())) '').format(option_bag.option.impl_get_display_name()))
return option_bag.config_bag.context.set_value(option_bag, return option_bag.config_bag.context.set_value(option_bag,
value, value,
) )
@option_type(['group', 'option']) @option_type(['group', 'option', 'with_index'])
def reset(self, def reset(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
is_group: bool=False, is_group: bool=False,
@ -723,26 +634,18 @@ class TiramisuOptionValue(CommonTiramisuOption):
self._config_bag, self._config_bag,
) )
else: else:
self._config_bag.context.delattr(option_bag) values = self._config_bag.context.get_values()
if option_bag.index is not None:
values.reset_follower(option_bag)
else:
values.reset(option_bag)
@option_type('option') @option_type(['option', 'with_index', 'symlink'])
def default(self, options_bag: List[OptionBag]): def default(self, options_bag: List[OptionBag]):
"""Get default value (default of option or calculated value)""" """Get default value (default of option or calculated value)"""
option_bag = options_bag[-1] return self._config_bag.context.get_values().get_default_value(options_bag[-1])
values = self._config_bag.context.get_values()
if option_bag.option.impl_is_symlinkoption():
value = []
length = self._config_bag.context.get_length_leadership(options_bag[-2])
for idx in range(length):
soption_bag = OptionBag(options_bag.option,
idx,
self._config_bag,
)
value.append(values.getdefaultvalue(soption_bag))
return value
return values.getdefaultvalue(option_bag)
@option_type('option') @option_type(['option', 'with_index'])
def valid(self, options_bag: List[OptionBag]): def valid(self, options_bag: List[OptionBag]):
"""The if the option's value is valid""" """The if the option's value is valid"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -757,7 +660,7 @@ class TiramisuOptionValue(CommonTiramisuOption):
return False return False
return True return True
@option_type('choice') @option_type(['choice', 'with_index'])
def list(self, options_bag: List[OptionBag]): def list(self, options_bag: List[OptionBag]):
"""All values available for a ChoiceOption""" """All values available for a ChoiceOption"""
option_bag = options_bag[-1] option_bag = options_bag[-1]
@ -766,19 +669,15 @@ class TiramisuOptionValue(CommonTiramisuOption):
@option_type('leader') @option_type('leader')
def pop(self, def pop(self,
options_bag: List[OptionBag], options_bag: List[OptionBag],
index: int): index: int,
):
"""Pop a value""" """Pop a value"""
option_bag = options_bag[-1] self._config_bag.context.get_values().reset_leadership(options_bag[-1],
if len(options_bag) > 1: options_bag[-2],
leadership_option_bag = options_bag[-2] index,
else: )
leadership_option_bag = None
self._config_bag.context.pop_leader(option_bag,
leadership_option_bag,
index,
)
@option_type(['leader', 'follower', 'with_index']) @option_type(['leader', 'follower', 'with_or_without_index'])
def len(self, options_bag: List[OptionBag]): def len(self, options_bag: List[OptionBag]):
"""Length for a follower option""" """Length for a follower option"""
return self._config_bag.context.get_length_leadership(options_bag[-2]) return self._config_bag.context.get_length_leadership(options_bag[-2])
@ -786,7 +685,7 @@ class TiramisuOptionValue(CommonTiramisuOption):
def _registers(_registers: Dict[str, type], def _registers(_registers: Dict[str, type],
prefix: str, prefix: str,
extra_type: Optional[type]=None): ):
for module_name in globals().keys(): for module_name in globals().keys():
if module_name != prefix and module_name.startswith(prefix): if module_name != prefix and module_name.startswith(prefix):
module = globals()[module_name] module = globals()[module_name]
@ -893,11 +792,11 @@ class TiramisuOption(CommonTiramisu, TiramisuConfig):
group_type=None, group_type=None,
): ):
"""List options (by default list only option)""" """List options (by default list only option)"""
return self._list2(options_bag[-1], return self._list(options_bag[-1],
type, type,
group_type, group_type,
recursive, recursive,
) )
def _load_dict(self, def _load_dict(self,
clearable: str="all", clearable: str="all",
@ -943,8 +842,7 @@ class TiramisuContextInformation(TiramisuConfig):
"""Get an information""" """Get an information"""
values = self._config_bag.context.get_values() values = self._config_bag.context.get_values()
try: try:
return values.get_information(self._config_bag, return values.get_information(None,
None,
name, name,
undefined, undefined,
) )
@ -1115,7 +1013,6 @@ class TiramisuContextProperty(TiramisuConfig):
def add(self, prop): def add(self, prop):
"""Add a config property""" """Add a config property"""
settings = self._config_bag.context.get_settings()
props = set(self.get()) props = set(self.get())
if prop not in props: if prop not in props:
props.add(prop) props.add(prop)
@ -1128,12 +1025,8 @@ class TiramisuContextProperty(TiramisuConfig):
props.remove(prop) props.remove(prop)
self._set(frozenset(props)) self._set(frozenset(props))
def get(self, def get(self):
default=False):
"""Get all config properties""" """Get all config properties"""
if default:
config = self._config_bag.context
properties = config.get_settings().get_context_properties(config.properties_cache)
return self._config_bag.properties return self._config_bag.properties
def _set(self, def _set(self,
@ -1168,7 +1061,6 @@ class TiramisuContextProperty(TiramisuConfig):
force_store_value = 'force_store_value' not in self._config_bag.properties force_store_value = 'force_store_value' not in self._config_bag.properties
else: else:
force_store_value = False force_store_value = False
settings = self._config_bag.context.get_settings()
self._config_bag.context.get_settings()._properties = deepcopy(properties) self._config_bag.context.get_settings()._properties = deepcopy(properties)
self._config_bag.context.reset_cache(None, None) self._config_bag.context.reset_cache(None, None)
self._reset_config_properties() self._reset_config_properties()
@ -1205,8 +1097,9 @@ class TiramisuContextProperty(TiramisuConfig):
raise ValueError(_('unknown type {}').format(type)) raise ValueError(_('unknown type {}').format(type))
def getdefault(self, def getdefault(self,
type: Optional[str]=None, type: Optional[str]=None,
when: Optional[str]=None) -> Set[str]: when: Optional[str]=None,
) -> Set[str]:
setting = self._config_bag.context.get_settings() setting = self._config_bag.context.get_settings()
if type is None and when is None: if type is None and when is None:
return setting.default_properties return setting.default_properties
@ -1216,15 +1109,12 @@ class TiramisuContextProperty(TiramisuConfig):
if type == 'read_only': if type == 'read_only':
if when == 'append': if when == 'append':
return setting.ro_append return setting.ro_append
else: return setting.ro_remove
return setting.ro_remove if type == 'read_write':
elif type == 'read_write':
if when == 'append': if when == 'append':
return setting.rw_append return setting.rw_append
else: return setting.rw_remove
return setting.rw_remove raise ValueError(_('unknown type {}').format(type))
else:
raise ValueError(_('unknown type {}').format(type))
class TiramisuContextPermissive(TiramisuConfig): class TiramisuContextPermissive(TiramisuConfig):
@ -1302,7 +1192,8 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
): ):
option = TiramisuOption(path, option = TiramisuOption(path,
None, None,
self._config_bag) self._config_bag,
)
if first: if first:
return option return option
options.append(option) options.append(option)
@ -1318,11 +1209,11 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
None, None,
self._config_bag, self._config_bag,
) )
return self._list2(root_option_bag, return self._list(root_option_bag,
type, type,
group_type, group_type,
recursive, recursive,
) )
def _load_dict(self, def _load_dict(self,
clearable="all", clearable="all",
@ -1336,10 +1227,12 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
def dict(self, def dict(self,
clearable="all", clearable="all",
remotable="minimum", remotable="minimum",
form=[], form=None,
force=False, force=False,
): ):
"""Convert config and option to tiramisu format""" """Convert config and option to tiramisu format"""
if form is None:
form = []
if force or self._tiramisu_dict is None: if force or self._tiramisu_dict is None:
self._load_dict(clearable, remotable) self._load_dict(clearable, remotable)
return self._tiramisu_dict.todict(form) return self._tiramisu_dict.todict(form)
@ -1356,7 +1249,6 @@ class _TiramisuContextConfigReset():
def reset(self): def reset(self):
"""Remove all datas to current config (informations, values, properties, ...)""" """Remove all datas to current config (informations, values, properties, ...)"""
# Option's values # Option's values
settings = self._config_bag.context.get_settings()
context_owner = self._config_bag.context.get_values().get_context_owner() context_owner = self._config_bag.context.get_values().get_context_owner()
self._config_bag.context.get_values()._values = {None: {None: [None, context_owner]}} self._config_bag.context.get_values()._values = {None: {None: [None, context_owner]}}
# Option's informations # Option's informations
@ -1470,6 +1362,7 @@ class _TiramisuContextMixConfig(_TiramisuContextGroupConfig, _TiramisuContextCon
def add(self, def add(self,
config): config):
"""Add config from MetaConfig""" """Add config from MetaConfig"""
# pylint: disable=protected-access
self._config_bag.context.add_config(config._config_bag.context) self._config_bag.context.add_config(config._config_bag.context)
def parents(self): def parents(self):
@ -1495,7 +1388,8 @@ class TiramisuContextCache(TiramisuConfig):
self._config_bag.context.reset_cache(None, None) self._config_bag.context.reset_cache(None, None)
def set_expiration_time(self, def set_expiration_time(self,
time: int) -> None: time: int,
) -> None:
"""Change expiration time value""" """Change expiration time value"""
self._config_bag.expiration_time = time self._config_bag.expiration_time = time
@ -1505,6 +1399,8 @@ class TiramisuContextCache(TiramisuConfig):
class TiramisuAPI(TiramisuHelp): class TiramisuAPI(TiramisuHelp):
"""TiramisuAPI common class
"""
_registers = {} _registers = {}
def __init__(self, def __init__(self,
@ -1520,16 +1416,19 @@ class TiramisuAPI(TiramisuHelp):
config_bag = self._config_bag config_bag = self._config_bag
return TiramisuDispatcherOption(config_bag, return TiramisuDispatcherOption(config_bag,
self._orig_config_bags) self._orig_config_bags)
elif subfunc in ['forcepermissive', 'unrestraint']: if subfunc in ['forcepermissive', 'unrestraint', 'nowarnings']:
if self._orig_config_bags: if self._orig_config_bags:
raise ConfigError(_('do not use unrestraint and forcepermissive together')) msg = _('do not use unrestraint, nowarnings or forcepermissive together')
raise ConfigError(msg)
config_bag = self._config_bag.copy() config_bag = self._config_bag.copy()
if subfunc == 'unrestraint': if subfunc == 'unrestraint':
config_bag.unrestraint() config_bag.unrestraint()
elif subfunc == 'nowarnings':
config_bag.nowarnings()
else: else:
config_bag.set_permissive() config_bag.set_permissive()
return TiramisuAPI(config_bag, [self._config_bag]) return TiramisuAPI(config_bag, [self._config_bag])
elif subfunc == 'config': if subfunc == 'config':
config_type = self._config_bag.context.impl_type config_type = self._config_bag.context.impl_type
if config_type == 'group': if config_type == 'group':
config = _TiramisuContextGroupConfig config = _TiramisuContextGroupConfig
@ -1541,7 +1440,7 @@ class TiramisuAPI(TiramisuHelp):
config = _TiramisuContextConfig config = _TiramisuContextConfig
return config(self._config_bag, return config(self._config_bag,
self._orig_config_bags) self._orig_config_bags)
elif subfunc in self._registers: if subfunc in self._registers:
config_bag = self._config_bag config_bag = self._config_bag
# del config_bag.permissives # del config_bag.permissives
return self._registers[subfunc](config_bag, return self._registers[subfunc](config_bag,
@ -1549,18 +1448,21 @@ class TiramisuAPI(TiramisuHelp):
raise ConfigError(_('please specify a valid sub function ({})').format(subfunc)) raise ConfigError(_('please specify a valid sub function ({})').format(subfunc))
def __dir__(self): def __dir__(self):
return list(self._registers.keys()) + ['unrestraint', 'forcepermissive', 'config'] return list(self._registers.keys()) + \
['unrestraint', 'forcepermissive', 'nowarnings', 'config']
class TiramisuDispatcherOption(TiramisuContextOption): class TiramisuDispatcherOption(TiramisuContextOption):
"""Select an option""" """Select an option"""
def __call__(self, def __call__(self,
path: str, path: str,
index: Optional[int]=None) -> TiramisuOption: index: Optional[int]=None,
) -> TiramisuOption:
"""Select an option by path""" """Select an option by path"""
return TiramisuOption(path, return TiramisuOption(path,
index, index,
self._config_bag) self._config_bag,
)
class Config(TiramisuAPI): class Config(TiramisuAPI):
@ -1591,18 +1493,23 @@ class Config(TiramisuAPI):
del self._config_bag.context del self._config_bag.context
del self._config_bag del self._config_bag
del self._orig_config_bags del self._orig_config_bags
except: except ConfigError:
pass pass
class MetaConfig(TiramisuAPI): class MetaConfig(TiramisuAPI):
"""MetaConfig object that enables us to handle the sub configuration's options with common root optiondescription""" """MetaConfig object that enables us to handle the sub configuration's options
with common root optiondescription
"""
# pylint: disable=too-few-public-methods
def __init__(self, def __init__(self,
children: 'Config'=[], children: 'Config'=None,
name=None, name=None,
optiondescription: Optional[OptionDescription]=None, optiondescription: Optional[OptionDescription]=None,
display_name=None display_name=None
) -> None: ) -> None:
if children is None:
children = []
if isinstance(children, KernelMetaConfig): if isinstance(children, KernelMetaConfig):
config = children config = children
else: else:
@ -1628,7 +1535,10 @@ class MetaConfig(TiramisuAPI):
class MixConfig(TiramisuAPI): class MixConfig(TiramisuAPI):
"""MixConfig object that enables us to handle the sub configuration's options with differents root optiondescription""" """MixConfig object that enables us to handle the sub configuration's options
with differents root optiondescription
"""
# pylint: disable=too-few-public-methods
def __init__(self, def __init__(self,
optiondescription: OptionDescription, optiondescription: OptionDescription,
children: List[Config], children: List[Config],
@ -1661,6 +1571,7 @@ class MixConfig(TiramisuAPI):
class GroupConfig(TiramisuAPI): class GroupConfig(TiramisuAPI):
"""GroupConfig that enables us to access the sub configuration's options""" """GroupConfig that enables us to access the sub configuration's options"""
# pylint: disable=too-few-public-methods
def __init__(self, def __init__(self,
children, children,
name=None, name=None,

View file

@ -24,6 +24,7 @@ from itertools import chain
from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning
from .i18n import _ from .i18n import _
from .setting import undefined, ConfigBag, OptionBag, Undefined from .setting import undefined, ConfigBag, OptionBag, Undefined
from .function import FUNCTION_WAITING_FOR_DICT
# ____________________________________________________________ # ____________________________________________________________
@ -58,15 +59,15 @@ class Param:
class ParamOption(Param): class ParamOption(Param):
__slots__ = ('todict', __slots__ = ('option',
'option',
'notraisepropertyerror', 'notraisepropertyerror',
'raisepropertyerror') 'raisepropertyerror',
)
def __init__(self, def __init__(self,
option: 'Option', option: 'Option',
notraisepropertyerror: bool=False, notraisepropertyerror: bool=False,
raisepropertyerror: bool=False, raisepropertyerror: bool=False,
todict: bool=False) -> None: ) -> None:
if __debug__ and not hasattr(option, 'impl_is_symlinkoption'): if __debug__ and not hasattr(option, 'impl_is_symlinkoption'):
raise ValueError(_('paramoption needs an option not {}').format(type(option))) raise ValueError(_('paramoption needs an option not {}').format(type(option)))
if option.impl_is_symlinkoption(): if option.impl_is_symlinkoption():
@ -75,7 +76,6 @@ class ParamOption(Param):
cur_opt = option cur_opt = option
assert isinstance(notraisepropertyerror, bool), _('param must have a boolean not a {} for notraisepropertyerror').format(type(notraisepropertyerror)) assert isinstance(notraisepropertyerror, bool), _('param must have a boolean not a {} for notraisepropertyerror').format(type(notraisepropertyerror))
assert isinstance(raisepropertyerror, bool), _('param must have a boolean not a {} for raisepropertyerror').format(type(raisepropertyerror)) assert isinstance(raisepropertyerror, bool), _('param must have a boolean not a {} for raisepropertyerror').format(type(raisepropertyerror))
self.todict = todict
self.option = cur_opt self.option = cur_opt
self.notraisepropertyerror = notraisepropertyerror self.notraisepropertyerror = notraisepropertyerror
self.raisepropertyerror = raisepropertyerror self.raisepropertyerror = raisepropertyerror
@ -90,12 +90,10 @@ class ParamDynOption(ParamOption):
notraisepropertyerror: bool=False, notraisepropertyerror: bool=False,
raisepropertyerror: bool=False, raisepropertyerror: bool=False,
optional: bool=False, optional: bool=False,
todict: bool=False,
) -> None: ) -> None:
super().__init__(option, super().__init__(option,
notraisepropertyerror, notraisepropertyerror,
raisepropertyerror, raisepropertyerror,
todict,
) )
self.suffix = suffix self.suffix = suffix
self.dynoptiondescription = dynoptiondescription self.dynoptiondescription = dynoptiondescription
@ -103,12 +101,11 @@ class ParamDynOption(ParamOption):
class ParamSelfOption(Param): class ParamSelfOption(Param):
__slots__ = ('todict', 'whole') __slots__ = ('whole')
def __init__(self, def __init__(self,
todict: bool=False, whole: bool=undefined,
whole: bool=undefined) -> None: ) -> None:
"""whole: send all value for a multi, not only indexed value""" """whole: send all value for a multi, not only indexed value"""
self.todict = todict
if whole is not undefined: if whole is not undefined:
self.whole = whole self.whole = whole
@ -206,23 +203,13 @@ class Calculation:
for_settings=for_settings, for_settings=for_settings,
) )
def has_index(self, current_option):
if hasattr(self, '_has_index'):
return self._has_index
self._has_index = False
for arg in chain(self.params.args, self.params.kwargs.values()):
if isinstance(arg, ParamOption) and arg.option.impl_get_leadership() and \
arg.option.impl_get_leadership().in_same_group(current_option):
self._has_index = True
break
return self._has_index
class Break(Exception): class Break(Exception):
pass pass
def manager_callback(callbk: Param, def manager_callback(callback: Callable,
param: Param,
option, option,
index: Optional[int], index: Optional[int],
orig_value, orig_value,
@ -231,10 +218,10 @@ def manager_callback(callbk: Param,
for_settings: bool, for_settings: bool,
) -> Any: ) -> Any:
"""replace Param by true value""" """replace Param by true value"""
def calc_index(callbk, index, same_leadership): def calc_index(param, index, same_leadership):
if index is not None: if index is not None:
if hasattr(callbk, 'whole'): if hasattr(param, 'whole'):
whole = callbk.whole whole = param.whole
else: else:
# if value is same_leadership, follower are isolate by default # if value is same_leadership, follower are isolate by default
# otherwise option is a whole option # otherwise option is a whole option
@ -243,7 +230,7 @@ def manager_callback(callbk: Param,
return index return index
return None return None
def calc_self(callbk, def calc_self(param,
option, option,
index, index,
value, value,
@ -251,10 +238,8 @@ def manager_callback(callbk: Param,
): ):
# index must be apply only if follower # index must be apply only if follower
is_follower = option.impl_is_follower() is_follower = option.impl_is_follower()
apply_index = calc_index(callbk, index, is_follower) apply_index = calc_index(param, index, is_follower)
if value is undefined or (apply_index is None and is_follower): if value is undefined or (apply_index is None and is_follower):
if config_bag is undefined:
return undefined
path = option.impl_getpath() path = option.impl_getpath()
option_bag = OptionBag(option, option_bag = OptionBag(option,
None, None,
@ -266,7 +251,7 @@ def manager_callback(callbk: Param,
) )
parent_option_bag, option_bag = get_option_bag(config_bag, parent_option_bag, option_bag = get_option_bag(config_bag,
option, option,
callbk, param,
apply_index, apply_index,
True, True,
properties=properties, properties=properties,
@ -276,17 +261,17 @@ def manager_callback(callbk: Param,
for idx in range(config_bag.context.get_length_leadership(parent_option_bag)): for idx in range(config_bag.context.get_length_leadership(parent_option_bag)):
parent_option_bag, option_bag = get_option_bag(config_bag, parent_option_bag, option_bag = get_option_bag(config_bag,
option, option,
callbk, param,
idx, idx,
True, True,
properties=properties, properties=properties,
) )
new_value.append(get_value(callbk, new_value.append(get_value(param,
option_bag, option_bag,
path, path,
)) ))
else: else:
new_value = get_value(callbk, new_value = get_value(param,
option_bag, option_bag,
path, path,
) )
@ -297,7 +282,7 @@ def manager_callback(callbk: Param,
value = value[apply_index] value = value[apply_index]
return value return value
def get_value(callbk, def get_value(param,
option_bag, option_bag,
path, path,
): ):
@ -306,14 +291,14 @@ def manager_callback(callbk: Param,
value = config_bag.context.get_value(option_bag) value = config_bag.context.get_value(option_bag)
except PropertiesOptionError as err: except PropertiesOptionError as err:
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation # raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if callbk.notraisepropertyerror or callbk.raisepropertyerror: if param.notraisepropertyerror or param.raisepropertyerror:
raise err from err raise err from err
raise ConfigError(_('unable to carry out a calculation for "{}"' raise ConfigError(_('unable to carry out a calculation for "{}"'
', {}').format(option.impl_get_display_name(), err), err) from err ', {}').format(option.impl_get_display_name(), err), err) from err
except ValueError as err: except ValueError as err:
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option_bag.option.impl_get_display_name(), err)) from err raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option_bag.option.impl_get_display_name(), err)) from err
except AttributeError as err: except AttributeError as err:
if isinstance(callbk, ParamDynOption) and callbk.optional: if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror # cannot acces, simulate a propertyerror
raise PropertiesOptionError(option_bag, raise PropertiesOptionError(option_bag,
['configerror'], ['configerror'],
@ -324,7 +309,7 @@ def manager_callback(callbk: Param,
def get_option_bag(config_bag, def get_option_bag(config_bag,
opt, opt,
callbk, param,
index_, index_,
self_calc, self_calc,
properties=undefined, properties=undefined,
@ -352,14 +337,14 @@ def manager_callback(callbk: Param,
) )
except PropertiesOptionError as err: except PropertiesOptionError as err:
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation # raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if callbk.notraisepropertyerror or callbk.raisepropertyerror: if param.notraisepropertyerror or param.raisepropertyerror:
raise err from err raise err from err
raise ConfigError(_('unable to carry out a calculation for "{}"' raise ConfigError(_('unable to carry out a calculation for "{}"'
', {}').format(option.impl_get_display_name(), err), err) from err ', {}').format(option.impl_get_display_name(), err), err) from err
except ValueError as err: except ValueError as err:
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option.impl_get_display_name(), err)) from err raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option.impl_get_display_name(), err)) from err
except AttributeError as err: except AttributeError as err:
if isinstance(callbk, ParamDynOption) and callbk.optional: if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror # cannot acces, simulate a propertyerror
raise PropertiesOptionError(options_bag[-1], raise PropertiesOptionError(options_bag[-1],
['configerror'], ['configerror'],
@ -375,11 +360,11 @@ def manager_callback(callbk: Param,
else: else:
return options_bag[-1] return options_bag[-1]
if isinstance(callbk, ParamValue): if isinstance(param, ParamValue):
return callbk.value return param.value
if isinstance(callbk, ParamInformation): if isinstance(param, ParamInformation):
if isinstance(callbk, ParamSelfInformation): if isinstance(param, ParamSelfInformation):
option_bag = OptionBag(option, option_bag = OptionBag(option,
index, index,
config_bag, config_bag,
@ -387,48 +372,47 @@ def manager_callback(callbk: Param,
else: else:
option_bag = None option_bag = None
try: try:
return config_bag.context.impl_get_information(config_bag, return config_bag.context.impl_get_information(option_bag,
option_bag, param.information_name,
callbk.information_name, param.default_value,
callbk.default_value,
) )
except ValueError as err: except ValueError as err:
raise ConfigError(_('option "{}" cannot be calculated: {}').format(option.impl_get_display_name(), raise ConfigError(_('option "{}" cannot be calculated: {}').format(option.impl_get_display_name(),
str(err), str(err),
)) ))
if isinstance(callbk, ParamIndex): if isinstance(param, ParamIndex):
return index return index
if isinstance(callbk, ParamSuffix): if isinstance(param, ParamSuffix):
if not option.issubdyn(): if not option.issubdyn():
raise ConfigError(_('option "{}" is not in a dynoptiondescription').format(option.impl_get_display_name())) raise ConfigError(_('option "{}" is not in a dynoptiondescription').format(option.impl_get_display_name()))
return option.impl_getsuffix() return option.impl_getsuffix()
if isinstance(callbk, ParamSelfOption): if isinstance(param, ParamSelfOption):
if leadership_must_have_index and option.impl_is_follower() and index is None: if leadership_must_have_index and option.impl_is_follower() and index is None:
raise Break() raise Break()
value = calc_self(callbk, value = calc_self(param,
option, option,
index, index,
orig_value, orig_value,
config_bag, config_bag,
) )
if not callbk.todict: if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return value return value
return {'name': option.impl_get_display_name(), return {'name': option.impl_get_display_name(),
'value': value, 'value': value,
} }
if isinstance(callbk, ParamOption): if isinstance(param, ParamOption):
callbk_option = callbk.option callbk_option = param.option
callbk_options = None callbk_options = None
if callbk_option.issubdyn(): if callbk_option.issubdyn():
found = False found = False
if isinstance(callbk, ParamDynOption): if isinstance(param, ParamDynOption):
subdyn = callbk.dynoptiondescription subdyn = param.dynoptiondescription
rootpath = subdyn.impl_getpath() + callbk.suffix rootpath = subdyn.impl_getpath() + param.suffix
suffix = callbk.suffix suffix = param.suffix
callbk_option = callbk_option.to_dynoption(rootpath, callbk_option = callbk_option.to_dynoption(rootpath,
suffix, suffix,
subdyn, subdyn,
@ -462,8 +446,6 @@ def manager_callback(callbk: Param,
callbk_options.append(doption) callbk_options.append(doption)
if leadership_must_have_index and callbk_option.impl_is_follower() and index is None: if leadership_must_have_index and callbk_option.impl_is_follower() and index is None:
raise Break() raise Break()
if config_bag is undefined:
return undefined
if callbk_options is None: if callbk_options is None:
callbk_options = [callbk_option] callbk_options = [callbk_option]
values = None values = None
@ -471,7 +453,7 @@ def manager_callback(callbk: Param,
values = [] values = []
for callbk_option in callbk_options: for callbk_option in callbk_options:
if index is not None and callbk_option.impl_get_leadership() and \ if index is not None and callbk_option.impl_get_leadership() and \
callbk_option.impl_get_leadership().in_same_group(option): callbk_option.impl_get_leadership().in_same_leadership(option):
if not callbk_option.impl_is_follower(): if not callbk_option.impl_is_follower():
# leader # leader
index_ = None index_ = None
@ -486,11 +468,11 @@ def manager_callback(callbk: Param,
path = callbk_option.impl_getpath() path = callbk_option.impl_getpath()
option_bag = get_option_bag(config_bag, option_bag = get_option_bag(config_bag,
callbk_option, callbk_option,
callbk, param,
index_, index_,
False, False,
) )
value = get_value(callbk, value = get_value(param,
option_bag, option_bag,
path, path,
) )
@ -500,12 +482,10 @@ def manager_callback(callbk: Param,
values.append(value) values.append(value)
if values is not None: if values is not None:
value = values value = values
if not callbk.todict: if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return value return value
return {'name': callbk_option.impl_get_display_name(), return {'name': callbk_option.impl_get_display_name(),
'value': value} 'value': value}
raise ConfigError(_('unknown callback type {} in option {}').format(callbk,
option.impl_get_display_name()))
def carry_out_calculation(option, def carry_out_calculation(option,
@ -540,9 +520,10 @@ def carry_out_calculation(option,
args = [] args = []
kwargs = {} kwargs = {}
if callback_params: if callback_params:
for key, callbk in chain(fake_items(callback_params.args), callback_params.kwargs.items()): for key, param in chain(fake_items(callback_params.args), callback_params.kwargs.items()):
try: try:
value = manager_callback(callbk, value = manager_callback(callback,
param,
option, option,
index, index,
orig_value, orig_value,
@ -550,16 +531,14 @@ def carry_out_calculation(option,
leadership_must_have_index, leadership_must_have_index,
for_settings, for_settings,
) )
if value is undefined:
return undefined
if key is None: if key is None:
args.append(value) args.append(value)
else: else:
kwargs[key] = value kwargs[key] = value
except PropertiesOptionError as err: except PropertiesOptionError as err:
if callbk.raisepropertyerror: if param.raisepropertyerror:
raise err raise err
if callbk.todict: if callback.__name__ in FUNCTION_WAITING_FOR_DICT:
if key is None: if key is None:
args.append({'propertyerror': str(err)}) args.append({'propertyerror': str(err)})
else: else:
@ -616,8 +595,6 @@ def calculate(option,
raise err raise err
error = err error = err
except Exception as err: except Exception as err:
# import traceback
# traceback.print_exc()
error = err error = err
if args or kwargs: if args or kwargs:
msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" ' msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" '

View file

@ -16,79 +16,91 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
from time import time from time import time
from .log import log
def _display_classname(obj): # pragma: no cover
return(obj.__class__.__name__.lower())
class Cache: class Cache:
"""cache object
"""
__slots__ = ('_cache',) __slots__ = ('_cache',)
def __init__(self): def __init__(self):
self._cache = {} self._cache = {}
def setcache(self, path, index, val, self_props, props, validated): def _get_path_index(self, option_bag):
if option_bag is None:
path = None
index = None
else:
path = option_bag.path
index = option_bag.index
return path, index
def getcache(self,
option_bag,
type_,
expiration=True,
):
"""get the cache value fot a specified path
"""
no_cache = False, None, False
path, index = self._get_path_index(option_bag)
if path not in self._cache or index not in self._cache[path]:
return no_cache
value, timestamp, validated = self._cache[path][index]
if type_ == 'context_props':
# cached value is settings properties so value is props
props = value
self_props = {}
else:
props = option_bag.config_bag.properties
if type_ == 'self_props':
# cached value is self_props
self_props = value
else:
self_props = option_bag.properties
if 'cache' in props or \
'cache' in self_props:
if expiration and timestamp and \
('expire' in props or \
'expire' in self_props):
ntime = int(time())
if timestamp + option_bag.config_bag.expiration_time >= ntime:
return True, value, validated
else:
return True, value, validated
return no_cache
def setcache(self,
option_bag,
val,
type_='values',
validated=True,
):
"""add val in cache for a specified path """add val in cache for a specified path
if follower, add index if follower, add index
""" """
if 'cache' in props or 'cache' in self_props: if type_ == 'values':
self._cache.setdefault(path, {})[index] = (val, int(time()), validated) if 'cache' not in option_bag.config_bag.properties and \
'cache' not in option_bag.properties:
return
elif (option_bag is None or 'cache' not in option_bag.config_bag.properties) and \
'cache' not in val:
return
path, index = self._get_path_index(option_bag)
self._cache.setdefault(path, {})[index] = (val, int(time()), validated)
def delcache(self, path): def delcache(self, path):
"""reset cache a a specified path
"""
if path in self._cache: if path in self._cache:
del self._cache[path] del self._cache[path]
def get_cached(self): def get_cached(self):
"""get cache values
"""
return self._cache return self._cache
def reset_all_cache(self): def reset_all_cache(self):
"""reset all cache values
"""
self._cache.clear() self._cache.clear()
def getcache(self,
path,
expiration_time,
index,
props,
self_props,
type_,
):
no_cache = False, None, False
if 'cache' in props or type_ == 'context_props':
values = self._cache.get(path)
if values is None:
indexed = None
else:
indexed = values.get(index)
if indexed is None:
return no_cache
value, timestamp, validated = indexed
if type_ == 'context_props':
# cached value is settings properties so value is props
props = value
elif type_ == 'self_props':
# if self_props is None, so cached value is self properties
# so value is self_props
self_props = value
# recheck "cache" value
if 'cache' in props:
if expiration_time and timestamp and \
('expire' in props or \
'expire' in self_props):
ntime = int(time())
if timestamp + expiration_time >= ntime:
# log.debug('getcache in cache (1) %s %s %s %s %s', path, value, _display_classname(self),
# id(self), index)
return True, value, validated
# else:
# log.debug('getcache expired value for path %s < %s',
# timestamp + expiration_time, ntime)
else:
# log.debug('getcache in cache (2) %s %s %s %s %s', path, value, _display_classname(self),
# id(self), index)
return True, value, validated
# log.debug('getcache %s with index %s not in %s cache',
# path, index, _display_classname(self))
return no_cache

View file

@ -18,16 +18,15 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"options handler global entry point" """options handler global entry point
"""
import weakref import weakref
from copy import copy, deepcopy from copy import copy, deepcopy
from typing import Optional, List, Any, Union from typing import Optional, List, Any, Union
from .error import PropertiesOptionError, ConfigError, ConflictError, \ from .error import PropertiesOptionError, ConfigError, ConflictError, \
LeadershipError LeadershipError
from .option import SynDynOptionDescription, DynOptionDescription, Leadership, Option from .option import DynOptionDescription, Leadership, Option
from .option.baseoption import BaseOption
from .setting import OptionBag, ConfigBag, Settings, undefined, groups from .setting import OptionBag, ConfigBag, Settings, undefined, groups
from .value import Values, owners from .value import Values, owners
from .i18n import _ from .i18n import _
@ -48,7 +47,6 @@ class _SubConfig:
def __init__(self, def __init__(self,
descr, descr,
context, context,
config_bag,
subpath=None, subpath=None,
): ):
""" Configuration option management class """ Configuration option management class
@ -60,11 +58,6 @@ class _SubConfig:
:type subpath: `str` with the path name :type subpath: `str` with the path name
""" """
# main option description # main option description
if __debug__ and (not isinstance(descr, (BaseOption, SynDynOptionDescription)) or
not descr.impl_is_optiondescription()):
msg = _(f'cannot create a sub config for "{descr.impl_get_display_name()}" '
f'this is a "{descr.__class__.__name__}", not an "OptionDescription"')
raise TypeError(msg)
self._impl_descr = descr self._impl_descr = descr
self._impl_context = context self._impl_context = context
self._impl_path = subpath self._impl_path = subpath
@ -74,16 +67,6 @@ class _SubConfig:
): ):
"""Get the length of leader option (useful to know follower's length) """Get the length of leader option (useful to know follower's length)
""" """
if option_bag.option.impl_is_symlinkoption():
root_option_bag = OptionBag(option_bag.config_bag.context.get_description(),
None,
config_bag,
)
option_bag = self.get_sub_option_bag(root_option_bag,
option_bag.option.impl_getopt().impl_getpath(),
option_bag.index,
True,
)[-1]
cconfig_bag = option_bag.config_bag.copy() cconfig_bag = option_bag.config_bag.copy()
cconfig_bag.remove_validation() cconfig_bag.remove_validation()
option_bag = OptionBag(option_bag.option.get_leader(), option_bag = OptionBag(option_bag.option.get_leader(),
@ -92,22 +75,22 @@ class _SubConfig:
) )
return len(self.get_value(option_bag)) return len(self.get_value(option_bag))
def get_path(self):
return self._impl_path
def get_context(self):
return self._impl_context()
def get_description(self): def get_description(self):
"""get root description
"""
assert self._impl_descr is not None, _('there is no option description for this config' assert self._impl_descr is not None, _('there is no option description for this config'
' (may be GroupConfig)') ' (may be GroupConfig)')
return self._impl_descr return self._impl_descr
def get_settings(self): def get_settings(self):
return self.get_context()._impl_settings """get settings object
"""
return self._impl_settings # pylint: disable=no-member
def get_values(self): def get_values(self):
return self.get_context()._impl_values """get values object
"""
return self._impl_values # pylint: disable=no-member
# ============================================================================= # =============================================================================
# CACHE # CACHE
@ -129,18 +112,24 @@ class _SubConfig:
) )
option_bag.config_bag.properties = option_bag.config_bag.properties | {'cache'} option_bag.config_bag.properties = option_bag.config_bag.properties | {'cache'}
else: else:
context = self.get_context() self._impl_values_cache.reset_all_cache() # pylint: disable=no-member
context._impl_values_cache.reset_all_cache() self.properties_cache.reset_all_cache() # pylint: disable=no-member
context.properties_cache.reset_all_cache()
def get_values_cache(self):
"""get cache for values
"""
return self._impl_values_cache # pylint: disable=no-member
def reset_one_option_cache(self, def reset_one_option_cache(self,
resetted_opts, resetted_opts,
option_bag, option_bag,
): ):
"""reset cache for one option
"""
if option_bag.path in resetted_opts: if option_bag.path in resetted_opts:
return return
resetted_opts.append(option_bag.path) resetted_opts.append(option_bag.path)
for woption in option_bag.option._get_dependencies(option_bag.option): for woption in option_bag.option._get_dependencies(option_bag.option): # pylint: disable=protected-access
option = woption() option = woption()
soption_bag = OptionBag(option, soption_bag = OptionBag(option,
option_bag.index, option_bag.index,
@ -178,7 +167,6 @@ class _SubConfig:
subpath = '' subpath = ''
for suffix in option_bag.option.get_suffixes(option_bag.config_bag): for suffix in option_bag.option.get_suffixes(option_bag.config_bag):
path_suffix = option_bag.option.convert_suffix_to_path(suffix)
doption = option_bag.option.to_dynoption(subpath, doption = option_bag.option.to_dynoption(subpath,
suffix, suffix,
option_bag.option, option_bag.option,
@ -192,7 +180,7 @@ class _SubConfig:
None, None,
option_bag.config_bag, option_bag.config_bag,
): ):
coption_bag = self.get_sub_option_bag(doption_bag, coption_bag = self.get_sub_option_bag(doption_bag, # pylint: disable=no-member
coption.impl_getpath(), coption.impl_getpath(),
None, None,
False, False,
@ -258,15 +246,12 @@ class _SubConfig:
:param first: return only one option if True, a list otherwise :param first: return only one option if True, a list otherwise
:return: find list or an exception if nothing has been found :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): def _filter_by_value(soption_bag):
try: value = self.get_value(soption_bag)
value = soption_bag.config_bag.context.get_value(soption_bag)
except PropertiesOptionError:
return False
if isinstance(value, list): if isinstance(value, list):
return byvalue in value return byvalue in value
else: return value == byvalue
return value == byvalue
found = False found = False
if only_path is not undefined: if only_path is not undefined:
@ -286,10 +271,10 @@ class _SubConfig:
) )
if byvalue is not undefined and not _filter_by_value(soption_bag): if byvalue is not undefined and not _filter_by_value(soption_bag):
continue continue
elif option_bag.config_bag.properties: if option_bag.config_bag.properties:
#remove option with propertyerror, ... #remove option with propertyerror, ...
try: try:
self.get_sub_option_bag(option_bag, self.get_sub_option_bag(option_bag, # pylint: disable=no-member
path, path,
None, None,
True, True,
@ -331,12 +316,15 @@ class _SubConfig:
return value return value
def walk(self, def walk(self,
option_bag, option_bag: OptionBag,
types=['option'], types: List[str]=('option',),
group_type=None, group_type=None,
recursive=True, recursive: bool=True,
walked=False, walked: bool=False,
): ):
"""walk to tree
"""
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments,
if option_bag.option.impl_is_optiondescription(): if option_bag.option.impl_is_optiondescription():
# do not return root option # do not return root option
if walked: if walked:
@ -349,7 +337,7 @@ class _SubConfig:
if not option_bag.option.impl_is_leadership(): if not option_bag.option.impl_is_leadership():
for opt in option_bag.option.get_children(option_bag.config_bag): for opt in option_bag.option.get_children(option_bag.config_bag):
try: try:
yield from self.walk(self.get_sub_option_bag(option_bag, yield from self.walk(self.get_sub_option_bag(option_bag, # pylint: disable=no-member
opt.impl_getpath(), opt.impl_getpath(),
None, None,
True, True,
@ -366,7 +354,7 @@ class _SubConfig:
# it's a leadership so walk to leader and followers # it's a leadership so walk to leader and followers
# followers has specific length # followers has specific length
leader, *followers = option_bag.option.get_children(option_bag.config_bag) leader, *followers = option_bag.option.get_children(option_bag.config_bag)
leader_option_bag = self.get_sub_option_bag(option_bag, leader_option_bag = self.get_sub_option_bag(option_bag, # pylint: disable=no-member
leader.impl_getpath(), leader.impl_getpath(),
None, None,
True, True,
@ -375,7 +363,7 @@ class _SubConfig:
values = self.get_value(leader_option_bag, values = self.get_value(leader_option_bag,
need_help=False, need_help=False,
) )
leadership_length = len(values) ls_length = len(values)
try: try:
self._walk_valid_value(leader_option_bag, self._walk_valid_value(leader_option_bag,
types, types,
@ -383,21 +371,19 @@ class _SubConfig:
) )
except PropertiesOptionError as err: except PropertiesOptionError as err:
if err.proptype in (['mandatory'], ['empty']): if err.proptype in (['mandatory'], ['empty']):
if 'mandatory' in types: yield leader_option_bag
yield leader_option_bag for idx in range(ls_length):
else:
raise err
for idx in range(leadership_length):
followers_dict[idx] = [] followers_dict[idx] = []
for follower in followers: for follower in followers:
follower_path = follower.impl_getpath() follower_path = follower.impl_getpath()
try: try:
for f_follower_bag in self.walk(self.get_sub_option_bag(option_bag, options_bag = self.get_sub_option_bag(option_bag, # pylint: disable=no-member
follower_path, follower_path,
idx, idx,
True, True,
leadership_length=leadership_length, leadership_length=ls_length,
)[-1], )
for f_follower_bag in self.walk(options_bag[-1],
types=types, types=types,
recursive=recursive, recursive=recursive,
group_type=group_type, group_type=group_type,
@ -410,7 +396,6 @@ class _SubConfig:
continue continue
if 'option' in types: if 'option' in types:
yield followers_dict yield followers_dict
# pass
else: else:
if 'mandatory' in types and not option_bag.option.impl_is_symlinkoption(): if 'mandatory' in types and not option_bag.option.impl_is_symlinkoption():
try: try:
@ -455,25 +440,13 @@ class _SubConfig:
def set_value(self, def set_value(self,
option_bag: OptionBag, option_bag: OptionBag,
value: Any, value: Any,
): ) -> Any:
if option_bag.option.impl_is_symlinkoption(): """set value
raise ConfigError(_("can't set value to a SymLinkOption")) """
context = option_bag.config_bag.context self.get_settings().validate_properties(option_bag)
context.get_settings().validate_properties(option_bag) return self.get_values().set_value(option_bag,
return context.get_values().set_value(option_bag, value
value, )
)
def delattr(self,
option_bag):
option = option_bag.option
if option.impl_is_symlinkoption():
raise ConfigError(_("can't delete a SymLinkOption"))
values = self.get_values()
if option_bag.index is not None:
values.reset_follower(option_bag)
else:
values.reset(option_bag)
def get_value(self, def get_value(self,
option_bag, option_bag,
@ -484,7 +457,9 @@ class _SubConfig:
:return: option's value if name is an option name, OptionDescription :return: option's value if name is an option name, OptionDescription
otherwise otherwise
""" """
option_bag = self._get(option_bag, parent_option_bag, need_help) option_bag = self._get(option_bag,
need_help,
)
if isinstance(option_bag, list): if isinstance(option_bag, list):
value = [] value = []
for opt_bag in option_bag: for opt_bag in option_bag:
@ -507,10 +482,10 @@ class _SubConfig:
return value return value
def _get(self, def _get(self,
option_bag, option_bag: OptionBag,
parent_option_bag, need_help: bool,
need_help, ) -> OptionBag:
): # pylint: disable=too-many-locals
option = option_bag.option option = option_bag.option
if option.impl_is_symlinkoption(): if option.impl_is_symlinkoption():
suboption = option.impl_getopt() suboption = option.impl_getopt()
@ -534,7 +509,7 @@ class _SubConfig:
ret.append(doption_bag) ret.append(doption_bag)
return ret return ret
if suboption.impl_is_follower(): if suboption.impl_is_follower():
options_bag = self.get_sub_option_bag(option_bag.config_bag, options_bag = self.get_sub_option_bag(option_bag.config_bag, # pylint: disable=no-member
suboption.impl_getpath(), suboption.impl_getpath(),
None, None,
True, True,
@ -554,40 +529,28 @@ class _SubConfig:
option_bag.config_bag, option_bag.config_bag,
ori_option=option, ori_option=option,
) )
return self._get(soption_bag, None, need_help) return self._get(soption_bag,
need_help,
)
return option_bag return option_bag
def get_owner(self, def get_owner(self, option_bag: OptionBag):
option_bag: OptionBag, """get owner
parent_option_bag=None, """
): options_bag = self._get(option_bag,
options_bag = self._get(option_bag, parent_option_bag, need_help=True) need_help=True,
)
if isinstance(options_bag, list): if isinstance(options_bag, list):
if not option_bag.option.impl_is_symlinkoption(): for opt_bag in options_bag:
owner = [] owner = self.get_owner(opt_bag)
for opt_bag in options_bag: if owner != owners.default:
owner.append(self.get_owner(opt_bag)) break
else: else:
for opt_bag in options_bag: owner = owners.default
owner = self.get_owner(opt_bag)
if owner != owners.default:
break
else:
owner = owners.default
else: else:
owner = self.get_values().getowner(options_bag) owner = self.get_values().getowner(options_bag)
return owner return owner
def pop_leader(self,
option_bag: OptionBag,
leadership_option_bag: OptionBag,
index: int,
):
self.get_values().reset_leadership(option_bag,
leadership_option_bag,
index,
)
class _CommonConfig(_SubConfig): class _CommonConfig(_SubConfig):
"abstract base class for the Config, KernelGroupConfig and the KernelMetaConfig" "abstract base class for the Config, KernelGroupConfig and the KernelMetaConfig"
@ -597,18 +560,21 @@ class _CommonConfig(_SubConfig):
'properties_cache', 'properties_cache',
'_impl_permissives_cache', '_impl_permissives_cache',
'parents', 'parents',
'impl_type') 'impl_type',
)
def _impl_build_all_caches(self, descr): def _impl_build_all_caches(self, descr):
if not descr.impl_already_build_caches(): if not descr.impl_already_build_caches():
descr._group_type = groups.root descr._group_type = groups.root # pylint: disable=protected-access
descr._build_cache(display_name=self._display_name) descr._build_cache(display_name=self._display_name) # pylint: disable=no-member,protected-access
if not hasattr(descr, '_cache_force_store_values'): if not hasattr(descr, '_cache_force_store_values'):
raise ConfigError(_('option description seems to be part of an other ' raise ConfigError(_('option description seems to be part of an other '
'config')) 'config'))
def get_parents(self): def get_parents(self):
for parent in self.parents: """get parents
"""
for parent in self.parents: # pylint: disable=no-member
yield parent() yield parent()
# information # information
@ -622,22 +588,19 @@ class _CommonConfig(_SubConfig):
:param key: information's key (ex: "help", "doc" :param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string") :param value: information's value (ex: "the help string")
""" """
self._impl_values.set_information(config_bag, self._impl_values.set_information(None, # pylint: disable=no-member
None, key,
key, value,
value, )
) cache = self.get_description()._cache_dependencies_information.get(key, []) # pylint: disable=protected-access
context = config_bag.context
cache = context.get_description()._cache_dependencies_information.get(key, [])
for option in cache: for option in cache:
option_bag = OptionBag(option, option_bag = OptionBag(option,
None, None,
config_bag, config_bag,
) )
context.reset_cache(option_bag) self.reset_cache(option_bag)
def impl_get_information(self, def impl_get_information(self,
config_bag,
option_bag, option_bag,
key, key,
default, default,
@ -646,8 +609,7 @@ class _CommonConfig(_SubConfig):
:param key: the item string (ex: "help") :param key: the item string (ex: "help")
""" """
return self._impl_values.get_information(config_bag, return self._impl_values.get_information(option_bag, # pylint: disable=no-member
option_bag,
key, key,
default, default,
) )
@ -656,24 +618,27 @@ class _CommonConfig(_SubConfig):
key, key,
raises=True, raises=True,
): ):
self._impl_values.del_information(key, """delete an information
"""
self._impl_values.del_information(key, # pylint: disable=no-member
raises, raises,
) )
def impl_list_information(self): def impl_list_information(self):
return self._impl_values.list_information() """list information keys for context
"""
return self._impl_values.list_information() # pylint: disable=no-member
def __getstate__(self): def gen_fake_values(self) -> 'KernelConfig':
raise NotImplementedError() """generate a fake values to improve validation when assign a new value
"""
def _gen_fake_values(self): export = deepcopy(self.get_values()._values) # pylint: disable=protected-access
export = deepcopy(self.get_values()._values)
fake_config = KernelConfig(self._impl_descr, fake_config = KernelConfig(self._impl_descr,
force_values=export, force_values=export,
force_settings=self.get_settings(), force_settings=self.get_settings(),
name=self._impl_name, name=self._impl_name, # pylint: disable=no-member
) )
fake_config.parents = self.parents fake_config.parents = self.parents # pylint: disable=no-member
return fake_config return fake_config
def duplicate(self, def duplicate(self,
@ -684,10 +649,11 @@ class _CommonConfig(_SubConfig):
deep=None, deep=None,
name=None, name=None,
): ):
if not isinstance(self, (KernelConfig, KernelMixConfig)): """duplication config
raise ConfigError(_('cannot duplicate {}').format(self.__class__.__name__)) """
# pylint: disable=too-many-arguments
if name is None: if name is None:
name = self._impl_name name = self._impl_name # pylint: disable=no-member
if isinstance(self, KernelConfig): if isinstance(self, KernelConfig):
duplicated_config = KernelConfig(self._impl_descr, duplicated_config = KernelConfig(self._impl_descr,
_duplicate=True, _duplicate=True,
@ -703,10 +669,10 @@ class _CommonConfig(_SubConfig):
) )
duplicated_values = duplicated_config.get_values() duplicated_values = duplicated_config.get_values()
duplicated_settings = duplicated_config.get_settings() duplicated_settings = duplicated_config.get_settings()
duplicated_values._values = deepcopy(self.get_values()._values) duplicated_values._values = deepcopy(self.get_values()._values) # pylint: disable=protected-access
duplicated_values._informations = deepcopy(self.get_values()._informations) duplicated_values._informations = deepcopy(self.get_values()._informations) # pylint: disable=protected-access
duplicated_settings._properties = deepcopy(self.get_settings()._properties) duplicated_settings._properties = deepcopy(self.get_settings()._properties) # pylint: disable=protected-access
duplicated_settings._permissives = deepcopy(self.get_settings()._permissives) duplicated_settings._permissives = deepcopy(self.get_settings()._permissives) # pylint: disable=protected-access
duplicated_settings.ro_append = self.get_settings().ro_append duplicated_settings.ro_append = self.get_settings().ro_append
duplicated_settings.rw_append = self.get_settings().rw_append duplicated_settings.rw_append = self.get_settings().rw_append
duplicated_settings.ro_remove = self.get_settings().ro_remove duplicated_settings.ro_remove = self.get_settings().ro_remove
@ -714,11 +680,11 @@ class _CommonConfig(_SubConfig):
duplicated_settings.default_properties = self.get_settings().default_properties duplicated_settings.default_properties = self.get_settings().default_properties
duplicated_config.reset_cache(None, None) duplicated_config.reset_cache(None, None)
if child is not None: if child is not None:
duplicated_config._impl_children.append(child) duplicated_config._impl_children.append(child) # pylint: disable=protected-access
child.parents.append(weakref.ref(duplicated_config)) child.parents.append(weakref.ref(duplicated_config))
if self.parents: if self.parents: # pylint: disable=no-member
if deep is not None: if deep is not None:
for parent in self.parents: for parent in self.parents: # pylint: disable=no-member
wparent = parent() wparent = parent()
if wparent not in deep: if wparent not in deep:
deep.append(wparent) deep.append(wparent)
@ -731,17 +697,19 @@ class _CommonConfig(_SubConfig):
name=subname, name=subname,
) )
else: else:
duplicated_config.parents = self.parents duplicated_config.parents = self.parents # pylint: disable=no-member
for parent in self.parents: for parent in self.parents: # pylint: disable=no-member
parent()._impl_children.append(duplicated_config) parent()._impl_children.append(duplicated_config) # pylint: disable=protected-access
return duplicated_config return duplicated_config
def get_config_path(self): def get_config_path(self):
"""get config path
"""
path = self.impl_getname() path = self.impl_getname()
for parent in self.parents: for parent in self.parents: # pylint: disable=no-member
wparent = parent() wparent = parent()
if wparent is None: # pragma: no cover if wparent is None: # pragma: no cover
raise ConfigError(_('parent of {} not already exists').format(self._impl_name)) raise ConfigError(_(f'parent of {self._impl_name} not already exists')) # pylint: disable=no-member
path = parent().get_config_path() + '.' + path path = parent().get_config_path() + '.' + path
return path return path
@ -755,6 +723,7 @@ class _CommonConfig(_SubConfig):
) -> List[OptionBag]: ) -> List[OptionBag]:
"""Get the suboption for path and the name of the option """Get the suboption for path and the name of the option
:returns: tuple (config, name)""" :returns: tuple (config, name)"""
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if isinstance(bag, ConfigBag): if isinstance(bag, ConfigBag):
option_bag = OptionBag(self.get_description(), option_bag = OptionBag(self.get_description(),
None, None,
@ -763,8 +732,6 @@ class _CommonConfig(_SubConfig):
else: else:
option_bag = bag option_bag = bag
if option_bag.option != option_bag.config_bag.context.get_description(): if option_bag.option != option_bag.config_bag.context.get_description():
if not path.startswith(option_bag.path + '.'):
raise ConfigError('cannot get sub option if not a parent')
path = path[len(option_bag.path) + 1:] path = path[len(option_bag.path) + 1:]
path = path.split('.') path = path.split('.')
last_idx = len(path) - 1 last_idx = len(path) - 1
@ -789,17 +756,18 @@ class _CommonConfig(_SubConfig):
option_index = None option_index = None
if idx == last_idx: if idx == last_idx:
if option_index is not None: if option_index is not None:
if option.impl_is_optiondescription() or option.impl_is_symlinkoption() or not option.impl_is_follower(): if option.impl_is_optiondescription() or \
option.impl_is_symlinkoption() 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')
if leadership_length is not None: if leadership_length is not None:
length = leadership_length length = leadership_length
else: else:
length = self.get_length_leadership(sub_option_bag) length = self.get_length_leadership(sub_option_bag)
if index >= length: if index >= length:
raise LeadershipError(_('index "{}" is greater than the leadership length "{}" ' raise LeadershipError(_(f'index "{index}" is greater than the leadership '
'for option "{}"').format(index, f'length "{length}" for option '
length, f'"{option.impl_get_display_name()}"'))
option.impl_get_display_name()))
option_properties = properties option_properties = properties
else: else:
option_properties = undefined option_properties = undefined
@ -815,17 +783,22 @@ class _CommonConfig(_SubConfig):
return options_bag return options_bag
def impl_getname(self): def impl_getname(self):
return self._impl_name """get config name
"""
return self._impl_name # pylint: disable=no-member
# ____________________________________________________________ # ____________________________________________________________
class KernelConfig(_CommonConfig): class KernelConfig(_CommonConfig):
"main configuration management entry" """main configuration management entry
"""
# pylint: disable=too-many-instance-attributes
__slots__ = ('__weakref__', __slots__ = ('__weakref__',
'_impl_name', '_impl_name',
'_display_name', '_display_name',
'_impl_symlink', '_impl_symlink',
'_storage') '_storage',
)
impl_type = 'config' impl_type = 'config'
def __init__(self, def __init__(self,
@ -843,6 +816,7 @@ class KernelConfig(_CommonConfig):
:param context: the current root config :param context: the current root config
:type context: `Config` :type context: `Config`
""" """
# pylint: disable=too-many-arguments,too-many-arguments
self._display_name = display_name self._display_name = display_name
self.parents = [] self.parents = []
self._impl_symlink = [] self._impl_symlink = []
@ -870,11 +844,12 @@ class KernelConfig(_CommonConfig):
super().__init__(descr, super().__init__(descr,
self._impl_context, self._impl_context,
None, None,
None,
) )
class KernelGroupConfig(_CommonConfig): class KernelGroupConfig(_CommonConfig):
"""Group a config with same optiondescription tree
"""
__slots__ = ('__weakref__', __slots__ = ('__weakref__',
'_impl_children', '_impl_children',
'_impl_name', '_impl_name',
@ -886,18 +861,15 @@ class KernelGroupConfig(_CommonConfig):
children, children,
display_name=None, display_name=None,
name=None, name=None,
_descr=None): _descr=None,
if not isinstance(children, list): ):
raise ConfigError(_("groupconfig's children must be a list")) # pylint: disable=super-init-not-called
names = [] names = []
for child in children: for child in children:
if not isinstance(child, _CommonConfig):
raise ConfigError(_("groupconfig's children must be Config, MetaConfig or "
"GroupConfig"))
name_ = child._impl_name name_ = child._impl_name
names.append(name_) names.append(name_)
if len(names) != len(set(names)): if len(names) != len(set(names)):
for idx in range(1, len(names) + 1): while range(1, len(names) + 1):
name = names.pop(0) name = names.pop(0)
if name in names: if name in names:
raise ConflictError(_('config name must be uniq in ' raise ConflictError(_('config name must be uniq in '
@ -905,9 +877,6 @@ class KernelGroupConfig(_CommonConfig):
self._impl_children = children self._impl_children = children
self.parents = [] self.parents = []
config_bag = ConfigBag(self,
properties=None,
permissives=None)
self._display_name = display_name self._display_name = display_name
if name: if name:
self._impl_name = name self._impl_name = name
@ -916,6 +885,8 @@ class KernelGroupConfig(_CommonConfig):
self._impl_path = None self._impl_path = None
def get_children(self): def get_children(self):
"""get all children
"""
return self._impl_children return self._impl_children
def reset_cache(self, def reset_cache(self,
@ -972,6 +943,7 @@ class KernelGroupConfig(_CommonConfig):
value, value,
) )
except PropertiesOptionError as err: except PropertiesOptionError as err:
# pylint: disable=protected-access
ret.append(PropertiesOptionError(err._option_bag, ret.append(PropertiesOptionError(err._option_bag,
err.proptype, err.proptype,
err._settings, err._settings,
@ -994,6 +966,7 @@ class KernelGroupConfig(_CommonConfig):
): ):
"""Find first not in current KernelGroupConfig, but in each children """Find first not in current KernelGroupConfig, but in each children
""" """
# pylint: disable=too-many-arguments
# if KernelMetaConfig, all children have same OptionDescription in # if KernelMetaConfig, all children have same OptionDescription in
# context so search only one time the option for all children # context so search only one time the option for all children
if bypath is undefined and byname is not None and \ if bypath is undefined and byname is not None and \
@ -1002,14 +975,13 @@ class KernelGroupConfig(_CommonConfig):
None, None,
config_bag, config_bag,
) )
for bypath, byoption in self.find(root_option_bag, next(self.find(root_option_bag,
bytype=None, bytype=None,
byname=byname, byname=byname,
byvalue=undefined, byvalue=undefined,
raise_if_not_found=raise_if_not_found, raise_if_not_found=raise_if_not_found,
with_option=True, with_option=True,
): ))
break
byname = None byname = None
ret = [] ret = []
@ -1035,25 +1007,30 @@ class KernelGroupConfig(_CommonConfig):
None, None,
cconfig_bag, cconfig_bag,
) )
for path in child.find(root_option_bag, try:
None, next(child.find(root_option_bag,
byname, None,
byvalue, byname,
raise_if_not_found=False, byvalue,
only_path=bypath, raise_if_not_found=False,
only_option=byoption, only_path=bypath,
): only_option=byoption,
))
ret.append(child) ret.append(child)
break except StopIteration:
pass
if not _sub: if not _sub:
self._find_return_results(ret != [], self._find_return_results(ret != [], # pylint: disable=use-implicit-booleaness-not-comparison
raise_if_not_found) raise_if_not_found,
)
return ret return ret
def reset(self, def reset(self,
path: str, path: str,
config_bag: ConfigBag, config_bag: ConfigBag,
): ) -> None:
"""reset value for specified path
"""
for child in self._impl_children: for child in self._impl_children:
settings = child.get_settings() settings = child.get_settings()
cconfig_bag = config_bag.copy() cconfig_bag = config_bag.copy()
@ -1072,7 +1049,10 @@ class KernelGroupConfig(_CommonConfig):
child.get_values().reset(option_bag) child.get_values().reset(option_bag)
def getconfig(self, def getconfig(self,
name): name: str,
) -> KernelConfig:
"""get a child from a config name
"""
for child in self._impl_children: for child in self._impl_children:
if name == child.impl_getname(): if name == child.impl_getname():
return child return child
@ -1080,9 +1060,12 @@ class KernelGroupConfig(_CommonConfig):
class KernelMixConfig(KernelGroupConfig): class KernelMixConfig(KernelGroupConfig):
__slots__ = ('_impl_name', """Kernel mixconfig: this config can have differents optiondescription tree
'_impl_symlink', """
'_storage') # pylint: disable=too-many-instance-attributes
__slots__ = ('_impl_symlink',
'_storage',
)
impl_type = 'mix' impl_type = 'mix'
def __init__(self, def __init__(self,
@ -1124,6 +1107,7 @@ class KernelMixConfig(KernelGroupConfig):
"""only_config: could be set if you want modify value in all Config included in """only_config: could be set if you want modify value in all Config included in
this KernelMetaConfig this KernelMetaConfig
""" """
# pylint: disable=too-many-branches,too-many-nested-blocks,too-many-locals,too-many-arguments
ret = [] ret = []
if only_config: if only_config:
if force_default or force_default_if_same or force_dont_change_value: if force_default or force_default_if_same or force_dont_change_value:
@ -1147,10 +1131,11 @@ class KernelMixConfig(KernelGroupConfig):
obj = self obj = self
else: else:
obj = child obj = child
validate_properties = not force_default and not force_default_if_same
moption_bag = obj.get_sub_option_bag(cconfig_bag, moption_bag = obj.get_sub_option_bag(cconfig_bag,
option_bag.path, option_bag.path,
option_bag.index, option_bag.index,
not force_default and not force_default_if_same, validate_properties,
)[-1] )[-1]
if force_default_if_same: if force_default_if_same:
if not child.get_values().hasvalue(option_bag.path): if not child.get_values().hasvalue(option_bag.path):
@ -1167,6 +1152,7 @@ class KernelMixConfig(KernelGroupConfig):
child_value, child_value,
) )
except PropertiesOptionError as err: except PropertiesOptionError as err:
# pylint: disable=protected-access
ret.append(PropertiesOptionError(err._option_bag, ret.append(PropertiesOptionError(err._option_bag,
err.proptype, err.proptype,
err._settings, err._settings,
@ -1198,10 +1184,13 @@ class KernelMixConfig(KernelGroupConfig):
return ret return ret
def reset(self, def reset(self,
path, path: str,
only_children, only_children: bool,
config_bag, config_bag: ConfigBag,
): ) -> None:
"""reset value for a specified path
"""
# pylint: disable=arguments-differ
rconfig_bag = config_bag.copy() rconfig_bag = config_bag.copy()
rconfig_bag.remove_validation() rconfig_bag.remove_validation()
if self.impl_type == 'meta': if self.impl_type == 'meta':
@ -1338,8 +1327,8 @@ class KernelMetaConfig(KernelMixConfig):
new_children = [] new_children = []
for child_name in children: for child_name in children:
assert isinstance(child_name, str), _('MetaConfig with optiondescription' assert isinstance(child_name, str), _('MetaConfig with optiondescription'
' must have string has child, ' ' must have string has child, '
'not {}').format(child_name) 'not {}').format(child_name)
new_children.append(KernelConfig(optiondescription, name=child_name)) new_children.append(KernelConfig(optiondescription, name=child_name))
children = new_children children = new_children
descr = optiondescription descr = optiondescription

View file

@ -12,6 +12,8 @@
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
"""some functions to validates or calculates value
"""
from typing import Any, List, Optional from typing import Any, List, Optional
from operator import add, mul, sub, truediv from operator import add, mul, sub, truediv
from ipaddress import ip_address, ip_interface, ip_network from ipaddress import ip_address, ip_interface, ip_network
@ -20,145 +22,130 @@ from .setting import undefined
from .error import display_list from .error import display_list
def valid_network_netmask(network: str, FUNCTION_WAITING_FOR_DICT = []
netmask: str):
"""FIXME
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
(value, name, ...)
""" """
if isinstance(network, dict): name = function.__name__
network_value = network['value'] if name not in FUNCTION_WAITING_FOR_DICT:
network_display_name = '({})'.format(network['name']) FUNCTION_WAITING_FOR_DICT.append(name)
else: return function
network_value = network
network_display_name = ''
if None in [network_value, netmask]: @function_waiting_for_dict
def valid_network_netmask(network: dict,
netmask: dict,
):
"""
validates if network and netmask are coherent
this validator must be set to netmask option
"""
if None in [network['value'], netmask['value']]:
return return
try: try:
ip_network('{0}/{1}'.format(network_value, netmask)) ip_network(f'{network["value"]}/{netmask["value"]}')
except ValueError: except ValueError as err:
raise ValueError(_('network "{0}" {1}does not match with this netmask').format(network_value, raise ValueError(_(f'network "{network["value"]}" ({network["name"]}) does not match '
network_display_name)) 'with this netmask')) from err
def valid_ip_netmask(ip: str,
netmask: str): @function_waiting_for_dict
if isinstance(ip, dict): def valid_ip_netmask(ip: dict, # pylint: disable=invalid-name
ip_value = ip['value'] netmask: dict,
ip_display_name = '({})'.format(ip['name']) ):
else: """validates if ip and netmask are coherent
ip_value = ip this validator must be set to netmask option
ip_display_name = '' """
if None in [ip_value, netmask]: if None in [ip['value'], netmask['value']]:
return return
ip_netmask = ip_interface('{0}/{1}'.format(ip_value, netmask)) ip_netmask = ip_interface(f'{ip["value"]}/{netmask["value"]}')
if ip_netmask.ip == ip_netmask.network.network_address: if ip_netmask.ip == ip_netmask.network.network_address:
raise ValueError(_('IP \"{0}\" {1}with this netmask is in fact a network address').format(ip_value, ip_display_name)) msg = _(f'IP "{ip["value"]}" ({ip["name"]}) with this netmask is '
elif ip_netmask.ip == ip_netmask.network.broadcast_address: 'in fact a network address')
raise ValueError(_('IP \"{0}\" {1}with this netmask is in fact a broacast address').format(ip_value, ip_display_name)) raise ValueError(msg)
if ip_netmask.ip == ip_netmask.network.broadcast_address:
msg = _(f'IP "{ip["value"]}" ({ip["name"]}) with this netmask is '
'in fact a broacast address')
raise ValueError(msg)
# FIXME CIDR ? @function_waiting_for_dict
def valid_broadcast(network: 'NetworkOption', def valid_broadcast(network: dict,
netmask: 'NetmaskOption', netmask: dict,
broadcast: 'BroadcastOption'): broadcast: dict,
if isinstance(network, dict): ):
network_value = network['value'] """validates if the broadcast is coherent with network and netmask
network_display_name = ' ({})'.format(network['name']) """
else: if None in [network['value'], netmask['value'], broadcast['value']]:
network_value = network
network_display_name = ''
if isinstance(netmask, dict):
netmask_value = netmask['value']
netmask_display_name = ' ({})'.format(netmask['name'])
else:
netmask_value = netmask
netmask_display_name = ''
if ip_network('{0}/{1}'.format(network, netmask)).broadcast_address != ip_address(broadcast):
raise ValueError(_('broadcast invalid with network {0}{1} and netmask {2}{3}'
'').format(network_value,
network_display_name,
netmask_value,
netmask_display_name))
def valid_in_network(ip,
network,
netmask=None):
if isinstance(network, dict):
network_value = network['value']
network_display_name = ' ({})'.format(network['name'])
else:
network_value = network
network_display_name = ''
if isinstance(netmask, dict):
netmask_value = netmask['value']
netmask_display_name = ' ({})'.format(netmask['name'])
else:
netmask_value = netmask
netmask_display_name = ''
if network_value is None:
return return
if '/' in network_value: if ip_network(f'{network["value"]}/{netmask["value"]}').broadcast_address != \
network_obj = ip_network('{0}'.format(network_value)) ip_address(broadcast['value']):
msg = _(f'broadcast invalid with network {network["value"]} ({network["name"]}) '
f'and netmask {netmask["value"]} ({netmask["name"]})')
raise ValueError(msg)
@function_waiting_for_dict
def valid_in_network(ip: dict, # pylint: disable=invalid-name
network: dict,
netmask=Optional[dict],
):
"""validates if an IP is in a network
this validator must be set to ip option
"""
if None in [ip['value'], network['value']]:
return
if '/' in network['value']:
# it's a CIDR network
network_value = network['value']
else: else:
if netmask_value is None: if netmask is None or netmask['value'] is None:
return return
network_obj = ip_network('{0}/{1}'.format(network_value, network_value = f'{network["value"]}/{netmask["value"]}'
netmask_value)) network_obj = ip_network(network_value)
if ip_interface(ip) not in network_obj: ip_netmask = ip_interface(f'{ip["value"]}/{network_obj.netmask}')
if ip_netmask not in network_obj:
if netmask is None: if netmask is None:
msg = _('this IP is not in network {0}{1}').format(network_value, msg = _('this IP is not in network {network["value"]} ({network["name"]})')
network_display_name)
else: else:
msg = _('this IP is not in network {0}{1} with netmask {2}{3}').format(network_value, msg = _('this IP is not in network {network["value"]} ({network["name"]}) '
network_display_name, 'with netmask {netmask["value"]} ({netmask["name"]})')
netmask_value,
netmask_display_name)
raise ValueError(msg) raise ValueError(msg)
# test if ip is not network/broadcast IP # test if ip is not network/broadcast IP
ip_netmask = ip_interface('{0}/{1}'.format(ip, network_obj.netmask))
if ip_netmask.ip == ip_netmask.network.network_address: if ip_netmask.ip == ip_netmask.network.network_address:
if netmask is None: msg = _(f'this IP with the network {network["value"]} ({network["value"]} '
msg = _('this IP with the network {0}{1} is in fact a network address').format(network_value, 'is in fact a network address')
network_display_name)
else:
msg = _('this IP with the netmask {0}{1} is in fact a network address').format(netmask_value,
netmask_display_name)
raise ValueError(msg) raise ValueError(msg)
elif ip_netmask.ip == ip_netmask.network.broadcast_address: if ip_netmask.ip == ip_netmask.network.broadcast_address:
if netmask is None: msg = _(f'this IP with the network {network["value"]} ({network["value"]} '
msg = _('this IP with the network {0}{1} is in fact a broadcast address').format(network_value, 'is in fact a broadcast address')
network_display_name)
else:
msg = _('this IP with the netmask {0}{1} is in fact a broadcast address').format(netmask_value,
netmask_display_name)
raise ValueError(msg) raise ValueError(msg)
@function_waiting_for_dict
def valid_not_equal(*values): def valid_not_equal(*values):
"""valid that two options have not same value
"""
equal = set() equal = set()
for idx, val in enumerate(values[1:]): for val in values[1:]:
if isinstance(val, dict): if 'propertyerror' in val:
if 'propertyerror' in val: continue
continue if values[0]['value'] == val['value'] is not None:
tval = val['value'] equal.add(val['name'])
else: if not equal:
tval = val return
if values[0] == tval is not None: msg = _(f'value is identical to {display_list(list(equal), add_quote=True)}')
if isinstance(val, dict): raise ValueError(msg)
if equal is True:
equal = set()
equal.add(val['name'])
elif not equal:
equal = True
if equal:
if equal is not True:
msg = _('value is identical to {}').format(display_list(list(equal), add_quote=True))
else:
msg = _('value is identical')
raise ValueError(msg)
class CalcValue: class CalcValue:
"""class to calc_value with different functions
"""
# pylint: disable=too-many-instance-attributes
def __call__(self, def __call__(self,
*args: List[Any], *args: List[Any],
multi: bool=False, multi: bool=False,
@ -175,6 +162,7 @@ class CalcValue:
operator: Optional[str]=None, operator: Optional[str]=None,
index: Optional[int]=None, index: Optional[int]=None,
**kwargs) -> Any: **kwargs) -> Any:
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals
"""calculate value """calculate value
:param args: list of value :param args: list of value
:param multi: value returns must be a list of value :param multi: value returns must be a list of value
@ -183,9 +171,11 @@ class CalcValue:
:param condition: test if condition is equal to expected value :param condition: test if condition is equal to expected value
if there is more than one condition, set condition_0, condition_1, ... if there is more than one condition, set condition_0, condition_1, ...
:param expected: value expected for all conditions :param expected: value expected for all conditions
if expected value is different between condition, set expected_0, expected_1, ... if expected value is different between condition, set expected_0,
expected_1, ...
:param no_condition_is_invalid: if no condition and not condition_0, condition_1, ... (for :param no_condition_is_invalid: if no condition and not condition_0, condition_1, ... (for
example if option is disabled) consider that condition not matching example if option is disabled) consider that condition not
matching
:param condition_operator: OR or AND operator for condition :param condition_operator: OR or AND operator for condition
:param allow_none: if False, do not return list in None is present in list :param allow_none: if False, do not return list in None is present in list
:param remove_duplicate_value: if True, remote duplicated value :param remove_duplicate_value: if True, remote duplicated value
@ -196,28 +186,36 @@ class CalcValue:
examples: examples:
* you want to copy value from an option to an other option: * you want to copy value from an option to an other option:
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, \
... Params, ParamOption
>>> val1 = StrOption('val1', '', 'val1') >>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1))) >>> val2 = StrOption('val2', '', callback=calc_value,
... callback_params=Params(ParamOption(val1)))
>>> od = OptionDescription('root', '', [val1, val2]) >>> od = OptionDescription('root', '', [val1, val2])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1'} {'val1': 'val1', 'val2': 'val1'}
* you want to copy values from two options in one multi option * you want to copy values from two options in one multi option
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1') >>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2') >>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True))) >>> val3 = StrOption('val3', "", multi=True, callback=calc_value,
... callback_params=Params((ParamOption(val1), ParamOption(val2)),
... multi=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3]) >>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']} {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
* you want to copy a value from an option if it not disabled, otherwise set 'default_value' * you want to copy a value from an option if it not disabled, otherwise set 'default_value'
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', '', 'val1') >>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), default=ParamValue('default_value'))) >>> val2 = StrOption('val2', '', callback=calc_value,
... callback_params=Params(ParamOption(val1, True),
... default=ParamValue('default_value')))
>>> od = OptionDescription('root', '', [val1, val2]) >>> od = OptionDescription('root', '', [val1, val2])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.property.read_write() >>> cfg.property.read_write()
@ -228,13 +226,15 @@ class CalcValue:
{'val2': 'default_value'} {'val2': 'default_value'}
* you want to copy value from an option if an other is True, otherwise set 'default_value' * you want to copy value from an option if an other is True, otherwise set 'default_value'
>>> from tiramisu import calc_value, BoolOption, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, BoolOption, StrOption, OptionDescription, Config, \
... Params, ParamOption, ParamValue
>>> boolean = BoolOption('boolean', '', True) >>> boolean = BoolOption('boolean', '', True)
>>> val1 = StrOption('val1', '', 'val1') >>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), >>> val2 = StrOption('val2', '', callback=calc_value,
... default=ParamValue('default_value'), ... callback_params=Params(ParamOption(val1, True),
... condition=ParamOption(boolean), ... default=ParamValue('default_value'),
... expected=ParamValue(True))) ... condition=ParamOption(boolean),
... expected=ParamValue(True)))
>>> od = OptionDescription('root', '', [boolean, val1, val2]) >>> od = OptionDescription('root', '', [boolean, val1, val2])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.property.read_write() >>> cfg.property.read_write()
@ -245,41 +245,55 @@ class CalcValue:
{'boolean': False, 'val1': 'val1', 'val2': 'default_value'} {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
* you want to copy option even if None is present * you want to copy option even if None is present
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1') >>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "") >>> val2 = StrOption('val2', "")
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True))) >>> val3 = StrOption('val3', "", multi=True, callback=calc_value,
... callback_params=Params((ParamOption(val1), ParamOption(val2)),
... multi=ParamValue(True), allow_none=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3]) >>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 'val1', 'val2': None, 'val3': ['val1', None]} {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
* you want uniq value * you want uniq value
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1') >>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val1') >>> val2 = StrOption('val2', "", 'val1')
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True))) >>> val3 = StrOption('val3', "", multi=True, callback=calc_value,
... callback_params=Params((ParamOption(val1), ParamOption(val2)),
... multi=ParamValue(True), remove_duplicate_value=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3]) >>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1', 'val3': ['val1']} {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
* you want to join two values with '.' * you want to join two values with '.'
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1') >>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2') >>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.'))) >>> val3 = StrOption('val3', "", callback=calc_value,
... callback_params=Params((ParamOption(val1),
... ParamOption(val2)), join=ParamValue('.')))
>>> od = OptionDescription('root', '', [val1, val2, val3]) >>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'} {'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'}
* you want join three values, only if almost three values are set * you want join three values, only if almost three values are set
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1') >>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2') >>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", 'val3') >>> val3 = StrOption('val3', "", 'val3')
>>> val4 = StrOption('val4', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2), ParamOption(val3, True)), join=ParamValue('.'), min_args_len=ParamValue(3))) >>> val4 = StrOption('val4', "", callback=calc_value,
... callback_params=Params((ParamOption(val1),
... ParamOption(val2),
... ParamOption(val3, True)),
... join=ParamValue('.'), min_args_len=ParamValue(3)))
>>> od = OptionDescription('root', '', [val1, val2, val3, val4]) >>> od = OptionDescription('root', '', [val1, val2, val3, val4])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.property.read_write() >>> cfg.property.read_write()
@ -290,25 +304,31 @@ class CalcValue:
{'val1': 'val1', 'val2': 'val2', 'val4': ''} {'val1': 'val1', 'val2': 'val2', 'val4': ''}
* you want to add all values * you want to add all values
>>> from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue >>> from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, \
... ParamOption, ParamValue
>>> val1 = IntOption('val1', "", 1) >>> val1 = IntOption('val1', "", 1)
>>> val2 = IntOption('val2', "", 2) >>> val2 = IntOption('val2', "", 2)
>>> val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add'))) >>> val3 = IntOption('val3', "", callback=calc_value,
... callback_params=Params((ParamOption(val1),
ParamOption(val2)),
... operator=ParamValue('add')))
>>> od = OptionDescription('root', '', [val1, val2, val3]) >>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od) >>> cfg = Config(od)
>>> cfg.value.dict() >>> cfg.value.dict()
{'val1': 1, 'val2': 2, 'val3': 3} {'val1': 1, 'val2': 2, 'val3': 3}
""" """
# pylint: disable=attribute-defined-outside-init
self.args = args self.args = args
self.condition = condition self.condition = condition
self.expected = expected self.expected = expected
self.condition_operator = condition_operator self.condition_operator = condition_operator
self.reverse_condition = reverse_condition self.reverse_condition = reverse_condition
self.kwargs = kwargs self.kwargs = kwargs
self.no_condition_is_invalid = no_condition_is_invalid self.no_condition_is_invalid = no_condition_is_invalid # pylint: disable=attribute-defined-outside-init
value = self.get_value(default, value = self.get_value(default,
min_args_len) min_args_len,
)
if not multi: if not multi:
if join is not None: if join is not None:
if None not in value: if None not in value:
@ -317,12 +337,13 @@ class CalcValue:
value = None value = None
elif value and operator: elif value and operator:
new_value = value[0] new_value = value[0]
op = {'mul': mul, oper = {'mul': mul,
'add': add, 'add': add,
'div': truediv, 'div': truediv,
'sub': sub}[operator] 'sub': sub,
}[operator]
for val in value[1:]: for val in value[1:]:
new_value = op(new_value, val) new_value = oper(new_value, val)
value = new_value value = new_value
elif value == []: elif value == []:
value = None value = None
@ -344,7 +365,9 @@ class CalcValue:
break break
lval = len(val) lval = len(val)
if length_val is not None and length_val != lval: if length_val is not None and length_val != lval:
raise ValueError(_(f'unexpected value in calc_value with join attribute "{val}" with invalid length "{length_val}"')) msg = _('unexpected value in calc_value with join attribute '
f'"{val}" with invalid length "{length_val}"')
raise ValueError(msg)
length_val = lval length_val = lval
new_value = [] new_value = []
if length_val is not None: if length_val is not None:
@ -374,6 +397,9 @@ class CalcValue:
pattern: str, pattern: str,
to_dict: bool=False, to_dict: bool=False,
empty_test=undefined) -> Any: empty_test=undefined) -> Any:
"""get value from kwargs
"""
# pylint: disable=too-many-branches
# if value attribute exist return it's value # if value attribute exist return it's value
# otherwise pattern_0, pattern_1, ... # otherwise pattern_0, pattern_1, ...
# otherwise undefined # otherwise undefined
@ -385,10 +411,9 @@ class CalcValue:
else: else:
kwargs_matches = {} kwargs_matches = {}
len_pattern = len(pattern) len_pattern = len(pattern)
for key in self.kwargs.keys(): for key, pattern_value in self.kwargs.items():
if key.startswith(pattern): if key.startswith(pattern):
index = int(key[len_pattern:]) index = int(key[len_pattern:])
pattern_value = self.kwargs[key]
if isinstance(pattern_value, dict): if isinstance(pattern_value, dict):
pattern_value = pattern_value['value'] pattern_value = pattern_value['value']
kwargs_matches[index] = pattern_value kwargs_matches[index] = pattern_value
@ -408,21 +433,28 @@ class CalcValue:
return returns return returns
def is_condition_matches(self, def is_condition_matches(self,
condition_value): condition_value,
):
"""verify the condition
"""
# pylint: disable=too-many-branches
calculated_conditions = self.value_from_kwargs(condition_value, calculated_conditions = self.value_from_kwargs(condition_value,
'condition_', 'condition_',
to_dict='all') to_dict='all',
)
if calculated_conditions is undefined: if calculated_conditions is undefined:
is_matches = not self.no_condition_is_invalid is_matches = not self.no_condition_is_invalid
else: else:
is_matches = None is_matches = None
calculated_expected = self.value_from_kwargs(self.expected, calculated_expected = self.value_from_kwargs(self.expected,
'expected_', 'expected_',
to_dict=True) to_dict=True,
)
calculated_reverse = self.value_from_kwargs(self.reverse_condition, calculated_reverse = self.value_from_kwargs(self.reverse_condition,
'reverse_condition_', 'reverse_condition_',
to_dict=True, to_dict=True,
empty_test=False) empty_test=False,
)
for idx, calculated_condition in calculated_conditions.items(): for idx, calculated_condition in calculated_conditions.items():
if isinstance(calculated_expected, dict): if isinstance(calculated_expected, dict):
if idx is not None: if idx is not None:
@ -453,14 +485,20 @@ class CalcValue:
if is_matches: if is_matches:
break break
else: else:
raise ValueError(_('unexpected {} condition_operator in calc_value').format(self.condition_operator)) msg = _(f'unexpected {self.condition_operator} condition_operator '
'in calc_value')
raise ValueError(msg)
is_matches = is_matches and not self.reverse_condition \ is_matches = is_matches and not self.reverse_condition \
or not is_matches and self.reverse_condition or not is_matches and self.reverse_condition
return is_matches return is_matches
def get_value(self, def get_value(self,
default, default,
min_args_len): min_args_len,
):
"""get the value from arguments
"""
# retrieve the condition
if isinstance(self.condition, dict): if isinstance(self.condition, dict):
if 'value' in self.condition: if 'value' in self.condition:
condition_value = self.condition['value'] condition_value = self.condition['value']
@ -468,18 +506,19 @@ class CalcValue:
condition_value = undefined condition_value = undefined
else: else:
condition_value = self.condition condition_value = self.condition
condition_matches = self.is_condition_matches(condition_value) # value is empty if condition doesn't match
if not condition_matches: # otherwise value is arg
# force to default if not self.is_condition_matches(condition_value):
value = [] value = []
else: else:
value = self.get_args() value = self.get_args()
if min_args_len and not len(value) >= min_args_len: if min_args_len and not len(value) >= min_args_len:
value = [] value = []
if value == []: if not value:
# default value # default value
new_default = self.value_from_kwargs(default, new_default = self.value_from_kwargs(default,
'default_') 'default_',
)
if new_default is not undefined: if new_default is not undefined:
if not isinstance(new_default, list): if not isinstance(new_default, list):
value = [new_default] value = [new_default]
@ -488,29 +527,34 @@ class CalcValue:
return value return value
def get_args(self): def get_args(self):
"""get all arguments
"""
return list(self.args) return list(self.args)
class CalcValuePropertyHelp(CalcValue): class CalcValuePropertyHelp(CalcValue):
"""special class to display property error
"""
def get_name(self): def get_name(self):
"""get the condition name
"""
return self.condition['name'] return self.condition['name']
def get_indexed_name(self, index): def get_indexed_name(self, index: int) -> str:
return self.kwargs.get(f'condition_{index}')['name'] """get name for a specified index
"""
condition_index = self.kwargs.get(f'condition_{index}')
if condition_index is not None and not isinstance(condition_index, dict):
raise ValueError(_(f'unexpected condition_{index} must have "todict" argument'))
return condition_index['name']
def has_condition_kwargs(self):
for condition in self.kwargs:
if condition.startswith('condition_'):
return True
return False
def build_arg(self, name, value): def build_property_message(self,
#if isinstance(option, tuple): name: str,
# if not reverse: value: Any,
# msg = _('the calculated value is {0}').format(display_value) ) -> str:
# else: """prepare message to display error message if needed
# msg = _('the calculated value is not {0}').format(display_value) """
#else:
if not self.reverse_condition: if not self.reverse_condition:
msg = _('the value of "{0}" is {1}').format(name, value) msg = _('the value of "{0}" is {1}').format(name, value)
else: else:
@ -519,40 +563,35 @@ class CalcValuePropertyHelp(CalcValue):
def get_args(self): def get_args(self):
args = super().get_args() args = super().get_args()
if args: action = args[0]
if len(self.args) != 1: calculated_expected = self.value_from_kwargs(self.expected,
raise ValueError(_('only one property is allowed for a calculation')) 'expected_',
action = args[0] to_dict=True)
calculated_expected = self.value_from_kwargs(self.expected, if self.condition is not undefined:
'expected_', if 'propertyerror' in self.condition:
to_dict=True) msg = self.condition['propertyerror']
if self.condition is not undefined:
if 'propertyerror' in self.condition:
msg = self.condition['propertyerror']
else:
name = self.get_name()
if isinstance(calculated_expected, dict):
calc_values = calculated_expected.values()
else:
calc_values = [calculated_expected]
display_value = display_list([str(val) for val in calc_values],
'or',
add_quote=True)
msg = self.build_arg(name, display_value)
elif self.has_condition_kwargs():
msgs = []
for key, value in calculated_expected.items():
name = self.get_indexed_name(key)
msgs.append(self.build_arg(name, f'"{value}"'))
msg = display_list(msgs, self.condition_operator.lower())
else: else:
return [(action, f'"{action}"')] name = self.get_name()
return [(action, f'"{action}" ({msg})')] if isinstance(calculated_expected, dict):
return calc_values = calculated_expected.values()
## calc_properties.setdefault(action, []).append(msg) else:
calc_values = [calculated_expected]
display_value = display_list([str(val) for val in calc_values],
'or',
add_quote=True)
msg = self.build_property_message(name, display_value)
else:
msgs = []
for key, value in calculated_expected.items():
name = self.get_indexed_name(key)
msgs.append(self.build_property_message(name, f'"{value}"'))
msg = display_list(msgs, self.condition_operator.lower())
return [(action, f'"{action}" ({msg})')]
calc_value = CalcValue() calc_value = CalcValue()
calc_value.__name__ = 'calc_value' calc_value.__name__ = 'calc_value' # pylint: disable=attribute-defined-outside-init
# function_waiting_for_dict(calc_value)
calc_value_property_help = CalcValuePropertyHelp() calc_value_property_help = CalcValuePropertyHelp()
calc_value_property_help.__name__ = 'calc_value_property_help' calc_value_property_help.__name__ = 'calc_value_property_help' # pylint: disable=attribute-defined-outside-init
function_waiting_for_dict(calc_value_property_help)

View file

@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
from logging import getLogger, DEBUG, basicConfig, StreamHandler, Formatter from logging import getLogger, DEBUG, StreamHandler, Formatter
import os import os

View file

@ -1,3 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2023 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
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# The original `Config` design model is unproudly borrowed from
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
"""all official option
"""
from .optiondescription import OptionDescription from .optiondescription import OptionDescription
from .dynoptiondescription import DynOptionDescription from .dynoptiondescription import DynOptionDescription
from .syndynoptiondescription import SynDynOptionDescription, SynDynLeadership from .syndynoptiondescription import SynDynOptionDescription, SynDynLeadership

View file

@ -18,18 +18,16 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from types import FunctionType """base option
from typing import FrozenSet, Callable, Tuple, Set, Optional, Union, Any, List """
from typing import FrozenSet, Set, Any, List
import weakref import weakref
from inspect import signature
from itertools import chain from itertools import chain
from ..i18n import _ from ..i18n import _
from ..setting import undefined, Settings from ..setting import undefined
from ..value import Values from ..autolib import Calculation, ParamOption
from ..error import ConfigError, display_list
from ..autolib import Calculation, Params, ParamOption
STATIC_TUPLE = frozenset() STATIC_TUPLE = frozenset()
@ -38,6 +36,8 @@ submulti = 2
def valid_name(name): def valid_name(name):
"""valid option name
"""
if not isinstance(name, str): if not isinstance(name, str):
return False return False
# if '.' in name: # if '.' in name:
@ -73,7 +73,8 @@ class Base:
elif isinstance(properties, tuple): elif isinstance(properties, tuple):
properties = frozenset(properties) properties = frozenset(properties)
if is_multi: if is_multi:
# if option is a multi, it cannot be 'empty' (None not allowed in the list) and cannot have multiple time the same value # if option is a multi, it cannot be 'empty' (None not allowed in the list)
# and cannot have multiple time the same value
# 'empty' and 'unique' are removed for follower's option # 'empty' and 'unique' are removed for follower's option
if 'notunique' not in properties: if 'notunique' not in properties:
properties = properties | {'unique'} properties = properties | {'unique'}
@ -85,7 +86,8 @@ class Base:
for prop in properties: for prop in properties:
if not isinstance(prop, str): if not isinstance(prop, str):
if not isinstance(prop, Calculation): if not isinstance(prop, Calculation):
raise ValueError(_('invalid property type {0} for {1}, must be a string or a Calculation').format(type(prop), name)) raise ValueError(_('invalid property type {0} for {1}, must be a string or a '
'Calculation').format(type(prop), name))
for param in chain(prop.params.args, prop.params.kwargs.values()): for param in chain(prop.params.args, prop.params.kwargs.values()):
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
param.option._add_dependency(self) param.option._add_dependency(self)
@ -96,7 +98,10 @@ class Base:
_setattr(self, '_properties', properties) _setattr(self, '_properties', properties)
def impl_has_dependency(self, def impl_has_dependency(self,
self_is_dep: bool=True) -> bool: self_is_dep: bool=True,
) -> bool:
"""this has dependency
"""
if self_is_dep is True: if self_is_dep is True:
return getattr(self, '_has_dependency', False) return getattr(self, '_has_dependency', False)
return hasattr(self, '_dependencies') return hasattr(self, '_dependencies')
@ -107,7 +112,7 @@ class Base:
ret = set(getattr(self, '_dependencies', STATIC_TUPLE)) ret = set(getattr(self, '_dependencies', STATIC_TUPLE))
if context_od and hasattr(context_od, '_dependencies'): if context_od and hasattr(context_od, '_dependencies'):
# add options that have context is set in calculation # add options that have context is set in calculation
return set(context_od._dependencies) | ret return set(context_od._dependencies) | ret # pylint: disable=protected-access
return ret return ret
def _get_suffixes_dependencies(self) -> Set[str]: def _get_suffixes_dependencies(self) -> Set[str]:
@ -119,26 +124,32 @@ class Base:
) -> None: ) -> None:
woption = weakref.ref(option) woption = weakref.ref(option)
options = self._get_dependencies(None) options = self._get_dependencies(None)
options.add(weakref.ref(option)) options.add(woption)
self._dependencies = tuple(options) self._dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init
if is_suffix: if is_suffix:
options = list(self._get_suffixes_dependencies()) options = list(self._get_suffixes_dependencies())
options.append(weakref.ref(option)) options.append(woption)
self._suffixes_dependencies = tuple(options) self._suffixes_dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init
def impl_is_optiondescription(self) -> bool: def impl_is_optiondescription(self) -> bool:
"""option is an option description
"""
return False return False
def impl_is_dynoptiondescription(self) -> bool: def impl_is_dynoptiondescription(self) -> bool:
"""option is not a dyn option description
"""
return False return False
def impl_getname(self) -> str: def impl_getname(self) -> str:
return self._name """get name
"""
return self._name # pylint: disable=no-member
def _set_readonly(self) -> None: def _set_readonly(self) -> None:
if isinstance(self._informations, dict): if isinstance(self._informations, dict): # pylint: disable=no-member
_setattr = object.__setattr__ _setattr = object.__setattr__
dico = self._informations dico = self._informations # pylint: disable=no-member
keys = tuple(dico.keys()) keys = tuple(dico.keys())
if len(keys) == 1: if len(keys) == 1:
dico = dico['doc'] dico = dico['doc']
@ -150,21 +161,30 @@ class Base:
_setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())])) _setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())]))
def impl_is_readonly(self) -> str: def impl_is_readonly(self) -> str:
"""the option is readonly
"""
# _path is None when initialise SymLinkOption # _path is None when initialise SymLinkOption
return hasattr(self, '_path') and self._path is not None return hasattr(self, '_path') and self._path is not None # pylint: disable=no-member
def impl_getproperties(self) -> FrozenSet[str]: def impl_getproperties(self) -> FrozenSet[str]:
"""get properties
"""
return getattr(self, '_properties', frozenset()) return getattr(self, '_properties', frozenset())
def _setsubdyn(self, def _setsubdyn(self,
subdyn, subdyn,
) -> None: ) -> None:
# pylint: disable=attribute-defined-outside-init
self._subdyn = subdyn self._subdyn = subdyn
def issubdyn(self) -> bool: def issubdyn(self) -> bool:
"""is sub dynoption
"""
return getattr(self, '_subdyn', None) is not None return getattr(self, '_subdyn', None) is not None
def getsubdyn(self): def getsubdyn(self):
"""get sub dynoption
"""
return self._subdyn() return self._subdyn()
# ____________________________________________________________ # ____________________________________________________________
@ -177,7 +197,7 @@ class Base:
:param key: the item string (ex: "help") :param key: the item string (ex: "help")
""" """
dico = self._informations dico = self._informations # pylint: disable=no-member
if isinstance(dico, tuple): if isinstance(dico, tuple):
if key in dico[0]: if key in dico[0]:
return dico[1][dico[0].index(key)] return dico[1][dico[0].index(key)]
@ -189,7 +209,9 @@ class Base:
return dico[key] return dico[key]
if default is not undefined: if default is not undefined:
return default return default
raise ValueError(_(f'information\'s item for "{self.impl_get_display_name()}" not found: "{key}"')) # pylint: disable=no-member
raise ValueError(_(f'information\'s item for "{self.impl_get_display_name()}" '
f'not found: "{key}"'))
def impl_set_information(self, def impl_set_information(self,
key: str, key: str,
@ -206,13 +228,15 @@ class Base:
" read-only").format(self.__class__.__name__, " read-only").format(self.__class__.__name__,
self, self,
key)) key))
self._informations[key] = value self._informations[key] = value # pylint: disable=no-member
def impl_list_information(self) -> Any: def impl_list_information(self) -> Any:
dico = self._informations """get the list of information keys
"""
dico = self._informations # pylint: disable=no-member
if isinstance(dico, tuple): if isinstance(dico, tuple):
return list(dico[0]) return list(dico[0])
elif isinstance(dico, str): if isinstance(dico, str):
return ['doc'] return ['doc']
# it's a dict # it's a dict
return list(dico.keys()) return list(dico.keys())
@ -225,9 +249,6 @@ class BaseOption(Base):
""" """
__slots__ = ('_display_name_function',) __slots__ = ('_display_name_function',)
def __getstate__(self):
raise NotImplementedError()
def __setattr__(self, def __setattr__(self,
name: str, name: str,
value: Any) -> Any: value: Any) -> Any:
@ -245,19 +266,16 @@ class BaseOption(Base):
' read-only').format(self.__class__.__name__, ' read-only').format(self.__class__.__name__,
self.impl_get_display_name(), self.impl_get_display_name(),
name)) name))
super(BaseOption, self).__setattr__(name, value) super().__setattr__(name, value)
def impl_getpath(self) -> str: def impl_getpath(self) -> str:
"""get the path of the option
"""
try: try:
return self._path return self._path
except AttributeError: except AttributeError as err:
raise AttributeError(_('"{}" not part of any Config').format(self.impl_get_display_name())) raise AttributeError(_(f'"{self.impl_get_display_name()}" not part of any Config')) \
from err
def impl_has_callback(self) -> bool:
"to know if a callback has been defined or not"
if self.impl_get_callback()[0] is not None:
print('ca existe')
return self.impl_get_callback()[0] is not None
def _impl_get_display_name(self, def _impl_get_display_name(self,
dyn_name: Base=None, dyn_name: Base=None,
@ -287,6 +305,8 @@ class BaseOption(Base):
) )
def impl_get_display_name(self) -> str: def impl_get_display_name(self) -> str:
"""get display name
"""
return self._get_display_name(None, return self._get_display_name(None,
None, None,
) )
@ -294,19 +314,26 @@ class BaseOption(Base):
def reset_cache(self, def reset_cache(self,
path: str, path: str,
config_bag: 'OptionBag', config_bag: 'OptionBag',
resetted_opts: List[Base]) -> None: resetted_opts: List[Base], # pylint: disable=unused-argument
) -> None:
"""reset cache
"""
context = config_bag.context context = config_bag.context
context.properties_cache.delcache(path) context.properties_cache.delcache(path)
context._impl_permissives_cache.delcache(path) context._impl_permissives_cache.delcache(path) # pylint: disable=protected-access
if not self.impl_is_optiondescription(): if not self.impl_is_optiondescription():
context._impl_values_cache.delcache(path) context.get_values_cache().delcache(path) # pylint: disable=protected-access
def impl_is_symlinkoption(self) -> bool: def impl_is_symlinkoption(self) -> bool:
"""the option is not a symlinkoption
"""
return False return False
def get_dependencies_information(self, def get_dependencies_information(self,
itself=False, itself=False,
) -> List[str]: ) -> List[str]:
"""get dependencies information
"""
if itself: if itself:
idx = 1 idx = 1
else: else:

View file

@ -18,19 +18,23 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""BoolOption
"""
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
class BoolOption(Option): class BoolOption(Option):
"represents a choice between ``True`` and ``False``" """represents a choice between ``True`` and ``False``
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'boolean' _type = _('boolean')
_display_name = _('boolean')
def validate(self, def validate(self,
value: bool) -> None: value: bool,
) -> None:
"""validate value
"""
if not isinstance(value, bool): if not isinstance(value, bool):
raise ValueError() raise ValueError()

View file

@ -18,21 +18,25 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from ipaddress import ip_address, ip_network """BroadcastOption
"""
from ipaddress import ip_address
from ..error import ConfigError
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
class BroadcastOption(Option): class BroadcastOption(Option):
"""represents the choice of a broadcast
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'broadcast_address' _type = _('broadcast address')
_display_name = _('broadcast address')
def validate(self, def validate(self,
value: str) -> None: value: str,
) -> None:
"""validate
"""
if not isinstance(value, str): if not isinstance(value, str):
raise ValueError(_('invalid string')) raise ValueError(_('invalid string'))
if value.count('.') != 3: if value.count('.') != 3:
@ -42,5 +46,5 @@ class BroadcastOption(Option):
raise ValueError() raise ValueError()
try: try:
ip_address(value) ip_address(value)
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""ChoiceOption
"""
from typing import Any from typing import Any
from ..setting import undefined, OptionBag from ..setting import undefined, OptionBag
@ -33,8 +35,7 @@ class ChoiceOption(Option):
The option can also have the value ``None`` The option can also have the value ``None``
""" """
__slots__ = tuple() __slots__ = tuple()
_type = 'choice' _type = _('choice')
_display_name = _('choice')
def __init__(self, def __init__(self,
name, name,
@ -56,7 +57,10 @@ class ChoiceOption(Option):
**kwargs) **kwargs)
def impl_get_values(self, def impl_get_values(self,
option_bag): option_bag: OptionBag,
):
"""get values allowed by option
"""
if isinstance(self._choice_values, Calculation): if isinstance(self._choice_values, Calculation):
values = self._choice_values.execute(option_bag) values = self._choice_values.execute(option_bag)
if values is not undefined and not isinstance(values, list): if values is not undefined and not isinstance(values, list):
@ -67,20 +71,18 @@ class ChoiceOption(Option):
return values return values
def validate(self, def validate(self,
value: Any) -> None: value: Any,
pass ) -> None:
"""nothing to valide
def sync_validate_with_option(self, """
value: Any,
option_bag: OptionBag) -> None:
if isinstance(self._choice_values, Calculation):
return
values = self._choice_values
self.validate_values(value, values)
def validate_with_option(self, def validate_with_option(self,
value: Any, value: Any,
option_bag: OptionBag) -> None: option_bag: OptionBag,
loaded: bool,
) -> None:
if loaded and isinstance(self._choice_values, Calculation):
return
values = self.impl_get_values(option_bag) values = self.impl_get_values(option_bag)
self.validate_values(value, values) self.validate_values(value, values)
@ -88,6 +90,8 @@ class ChoiceOption(Option):
value, value,
values, values,
) -> None: ) -> None:
"""validate values
"""
if values is not undefined and value not in values: if values is not undefined and value not in values:
if len(values) == 1: if len(values) == 1:
raise ValueError(_('only "{0}" is allowed' raise ValueError(_('only "{0}" is allowed'

View file

@ -18,22 +18,24 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""DateOption
"""
from datetime import datetime from datetime import datetime
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .stroption import StrOption from .stroption import StrOption
class DateOption(StrOption): class DateOption(StrOption):
"""represents the choice of a date
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'date' _type = _('date')
_display_name = _('date')
def validate(self, def validate(self,
value: str) -> None: value: str) -> None:
super().validate(value) super().validate(value)
try: try:
datetime.strptime(value, "%Y-%m-%d") datetime.strptime(value, "%Y-%m-%d")
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""DomainnameOption
"""
import re import re
from ipaddress import ip_interface from ipaddress import ip_interface
from typing import Any, Optional, List from typing import Any, Optional, List
@ -38,8 +40,7 @@ class DomainnameOption(StrOption):
fqdn: with tld, not supported yet fqdn: with tld, not supported yet
""" """
__slots__ = tuple() __slots__ = tuple()
_type = 'domainname' _type = _('domain name')
_display_name = _('domain name')
def __init__(self, def __init__(self,
name: str, name: str,
@ -54,8 +55,9 @@ class DomainnameOption(StrOption):
allow_cidr_network: bool=False, allow_cidr_network: bool=False,
type: str='domainname', type: str='domainname',
allow_without_dot: bool=False, allow_without_dot: bool=False,
allow_startswith_dot: bool=False) -> None: allow_startswith_dot: bool=False,
) -> None:
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if type not in ['netbios', 'hostname', 'domainname']: if type not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type {0} for hostname').format(type)) raise ValueError(_('unknown type {0} for hostname').format(type))
extra = {'_dom_type': type} extra = {'_dom_type': type}
@ -111,11 +113,10 @@ class DomainnameOption(StrOption):
warnings_only=warnings_only, warnings_only=warnings_only,
extra=extra) extra=extra)
def _get_len(self, type): def _get_len(self, type_):
if type == 'netbios': if type_ == 'netbios':
return 15 return 15
else: return 63
return 63
def _validate_domain(self, def _validate_domain(self,
value: str) -> None: value: str) -> None:

View file

@ -18,9 +18,11 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""DynOptionDescription
"""
import re import re
import weakref import weakref
from typing import List, Callable, Any from typing import List, Any
from itertools import chain from itertools import chain
from ..autolib import ParamOption from ..autolib import ParamOption
@ -28,7 +30,7 @@ from ..autolib import ParamOption
from ..i18n import _ from ..i18n import _
from .optiondescription import OptionDescription from .optiondescription import OptionDescription
from .baseoption import BaseOption from .baseoption import BaseOption
from ..setting import OptionBag, ConfigBag, groups, undefined from ..setting import OptionBag, ConfigBag
from ..error import ConfigError from ..error import ConfigError
from ..autolib import Calculation from ..autolib import Calculation
@ -37,6 +39,8 @@ NAME_REGEXP = re.compile(r'^[a-zA-Z\d\-_]*$')
class DynOptionDescription(OptionDescription): class DynOptionDescription(OptionDescription):
"""dyn option description
"""
__slots__ = ('_suffixes',) __slots__ = ('_suffixes',)
def __init__(self, def __init__(self,
@ -46,7 +50,7 @@ class DynOptionDescription(OptionDescription):
suffixes: Calculation, suffixes: Calculation,
properties=None, properties=None,
) -> None: ) -> None:
# pylint: disable=too-many-arguments
super().__init__(name, super().__init__(name,
doc, doc,
children, children,
@ -69,6 +73,8 @@ class DynOptionDescription(OptionDescription):
def convert_suffix_to_path(self, def convert_suffix_to_path(self,
suffix: Any, suffix: Any,
) -> str: ) -> str:
"""convert suffix to use it to a path
"""
if suffix is None: if suffix is None:
return None return None
if not isinstance(suffix, str): if not isinstance(suffix, str):
@ -78,7 +84,10 @@ class DynOptionDescription(OptionDescription):
return suffix return suffix
def get_suffixes(self, def get_suffixes(self,
config_bag: ConfigBag) -> List[str]: config_bag: ConfigBag,
) -> List[str]:
"""get dynamic suffixes
"""
option_bag = OptionBag(self, option_bag = OptionBag(self,
None, None,
config_bag, config_bag,
@ -90,8 +99,9 @@ class DynOptionDescription(OptionDescription):
values_ = [] values_ = []
if __debug__: if __debug__:
if not isinstance(values, list): if not isinstance(values, list):
raise ValueError(_('DynOptionDescription suffixes for option "{}", is not a list ({})' raise ValueError(_('DynOptionDescription suffixes for '
'').format(self.impl_get_display_name(), values)) f'option "{self.impl_get_display_name()}", is not '
f'a list ({values})'))
for val in values: for val in values:
cval = self.convert_suffix_to_path(val) cval = self.convert_suffix_to_path(val)
if not isinstance(cval, str) or re.match(NAME_REGEXP, cval) is None: if not isinstance(cval, str) or re.match(NAME_REGEXP, cval) is None:
@ -106,8 +116,8 @@ class DynOptionDescription(OptionDescription):
extra_values = values_.copy() extra_values = values_.copy()
for val in set(values_): for val in set(values_):
extra_values.remove(val) extra_values.remove(val)
raise ValueError(_('DynOptionDescription suffixes return a list with multiple value ' raise ValueError(_('DynOptionDescription suffixes return a list with '
'"{}"''').format(extra_values)) f'multiple value "{extra_values}"'''))
return values_ return values_
def impl_is_dynoptiondescription(self) -> bool: def impl_is_dynoptiondescription(self) -> bool:

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""EmailOption
"""
import re import re
from ..i18n import _ from ..i18n import _
@ -25,7 +27,8 @@ from .stroption import RegexpOption
class EmailOption(RegexpOption): class EmailOption(RegexpOption):
"""represents a choice of an email
"""
__slots__ = tuple() __slots__ = tuple()
_regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") _regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
_type = 'email' _type = _('email address')
_display_name = _('email address')

View file

@ -18,16 +18,17 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import re """FilenameOption
"""
from ..i18n import _ from ..i18n import _
from .stroption import StrOption from .stroption import StrOption
class FilenameOption(StrOption): class FilenameOption(StrOption):
"""represents a choice of a file name
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'filename' _type = _('file name')
_display_name = _('file name')
def validate(self, def validate(self,
value: str, value: str,

View file

@ -18,17 +18,18 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""FloatOption
"""
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
class FloatOption(Option): class FloatOption(Option):
"represents a choice of a floating point number" """represents a choice of a floating point number
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'float' _type = _('float')
_display_name = _('float')
def validate(self, def validate(self,
value: float) -> None: value: float) -> None:

View file

@ -18,8 +18,9 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""IntOption
"""
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
@ -27,8 +28,7 @@ from .option import Option
class IntOption(Option): class IntOption(Option):
"represents a choice of an integer" "represents a choice of an integer"
__slots__ = tuple() __slots__ = tuple()
_type = 'integer' _type = _('integer')
_display_name = _('integer')
def __init__(self, def __init__(self,
*args, *args,
@ -43,7 +43,8 @@ class IntOption(Option):
super().__init__(*args, extra=extra, **kwargs) super().__init__(*args, extra=extra, **kwargs)
def validate(self, def validate(self,
value: int) -> None: value: int,
) -> None:
if not isinstance(value, int): if not isinstance(value, int):
raise ValueError() raise ValueError()

View file

@ -18,21 +18,19 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""IPOption
"""
from ipaddress import ip_address, ip_interface from ipaddress import ip_address, ip_interface
from ..error import ConfigError
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option
from .stroption import StrOption from .stroption import StrOption
from ..function import valid_ip_netmask
class IPOption(StrOption): class IPOption(StrOption):
"represents the choice of an ip" """represents the choice of an ip
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'ip' _type = _('IP')
_display_name = _('IP')
def __init__(self, def __init__(self,
*args, *args,
@ -52,19 +50,19 @@ class IPOption(StrOption):
def _validate_cidr(self, value): def _validate_cidr(self, value):
try: try:
ip = ip_interface(value) ip_obj = ip_interface(value)
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err
if ip.ip == ip.network.network_address: if ip_obj.ip == ip_obj.network.network_address:
raise ValueError(_("it's in fact a network address")) raise ValueError(_("it's in fact a network address"))
elif ip.ip == ip.network.broadcast_address: if ip_obj.ip == ip_obj.network.broadcast_address:
raise ValueError(_("it's in fact a broacast address")) raise ValueError(_("it's in fact a broacast address"))
def _validate_ip(self, value): def _validate_ip(self, value):
try: try:
new_value = str(ip_address(value)) str(ip_address(value))
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err
def validate(self, def validate(self,
value: str) -> None: value: str) -> None:
@ -79,14 +77,14 @@ class IPOption(StrOption):
def second_level_validation(self, def second_level_validation(self,
value: str, value: str,
warnings_only: bool) -> None: warnings_only: bool) -> None:
ip = ip_interface(value) ip_obj = ip_interface(value)
if not self.impl_get_extra('_allow_reserved') and ip.is_reserved: if not self.impl_get_extra('_allow_reserved') and ip_obj.is_reserved:
if warnings_only: if warnings_only:
msg = _("shouldn't be reserved IP") msg = _("shouldn't be reserved IP")
else: else:
msg = _("mustn't be reserved IP") msg = _("mustn't be reserved IP")
raise ValueError(msg) raise ValueError(msg)
if self.impl_get_extra('_private_only') and not ip.is_private: if self.impl_get_extra('_private_only') and not ip_obj.is_private:
if warnings_only: if warnings_only:
msg = _("should be private IP") msg = _("should be private IP")
else: else:

View file

@ -20,22 +20,23 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import weakref import weakref
from itertools import chain from typing import List, Iterator, Optional
from typing import List, Iterator, Optional, Any
from ..i18n import _ from ..i18n import _
from ..setting import groups, undefined, OptionBag, Settings, ALLOWED_LEADER_PROPERTIES from ..setting import groups, undefined, OptionBag, ALLOWED_LEADER_PROPERTIES
from ..value import Values
from .optiondescription import OptionDescription from .optiondescription import OptionDescription
from .syndynoptiondescription import SynDynLeadership from .syndynoptiondescription import SynDynLeadership
from .baseoption import BaseOption from .baseoption import BaseOption
from .option import Option from .option import Option
from ..error import LeadershipError from ..error import LeadershipError
from ..autolib import Calculation, ParamOption from ..autolib import Calculation
class Leadership(OptionDescription): class Leadership(OptionDescription):
"""Leadership
"""
# pylint: disable=too-many-arguments
__slots__ = ('leader', __slots__ = ('leader',
'followers', 'followers',
) )
@ -57,35 +58,10 @@ class Leadership(OptionDescription):
leader = children[0] leader = children[0]
for idx, child in enumerate(children): for idx, child in enumerate(children):
if __debug__: if __debug__:
if child.impl_is_symlinkoption(): self._check_child_is_valid(child)
raise ValueError(_('leadership "{0}" shall not have '
"a symlinkoption").format(self.impl_get_display_name()))
if not isinstance(child, Option):
raise ValueError(_('leadership "{0}" shall not have '
'a subgroup').format(self.impl_get_display_name()))
if not child.impl_is_multi():
raise ValueError(_('only multi option allowed in leadership "{0}" but option '
'"{1}" is not a multi'
'').format(self.impl_get_display_name(),
child.impl_get_display_name()))
if idx != 0:
default = child.impl_getdefault()
if default != []:
if child.impl_is_submulti() and isinstance(default, tuple):
for val in default:
if not isinstance(val, Calculation):
calculation = False
else:
# empty default is valid
calculation = True
else:
calculation = isinstance(default, Calculation)
if not calculation:
raise ValueError(_('not allowed default value for follower option "{0}" '
'in leadership "{1}"'
'').format(child.impl_get_display_name(),
self.impl_get_display_name()))
if idx != 0: if idx != 0:
if __debug__:
self._check_default_value(child)
# remove empty property for follower # remove empty property for follower
child._properties = frozenset(child._properties - {'empty', 'unique'}) child._properties = frozenset(child._properties - {'empty', 'unique'})
followers.append(child) followers.append(child)
@ -96,35 +72,78 @@ class Leadership(OptionDescription):
if prop not in ALLOWED_LEADER_PROPERTIES and not isinstance(prop, Calculation): if prop not in ALLOWED_LEADER_PROPERTIES and not isinstance(prop, Calculation):
raise LeadershipError(_('leader cannot have "{}" property').format(prop)) raise LeadershipError(_('leader cannot have "{}" property').format(prop))
def _check_child_is_valid(self, child: BaseOption):
if child.impl_is_symlinkoption():
raise ValueError(_('leadership "{0}" shall not have '
"a symlinkoption").format(self.impl_get_display_name()))
if not isinstance(child, Option):
raise ValueError(_('leadership "{0}" shall not have '
'a subgroup').format(self.impl_get_display_name()))
if not child.impl_is_multi():
raise ValueError(_('only multi option allowed in leadership "{0}" but option '
'"{1}" is not a multi'
'').format(self.impl_get_display_name(),
child.impl_get_display_name()))
def _check_default_value(self, child: BaseOption):
default = child.impl_getdefault()
if default != []:
if child.impl_is_submulti() and isinstance(default, tuple):
for val in default:
if not isinstance(val, Calculation):
calculation = False
break
else:
# empty default is valid
calculation = True
else:
calculation = isinstance(default, Calculation)
if not calculation:
raise ValueError(_('not allowed default value for follower option '
'"{child.impl_get_display_name()}" in leadership '
'"{self.impl_get_display_name()}"'))
def _setsubdyn(self, def _setsubdyn(self,
subdyn, subdyn,
) -> None: ) -> None:
# pylint: disable=attribute-defined-outside-init,protected-access
for chld in self._children[1]: for chld in self._children[1]:
chld._setsubdyn(subdyn) chld._setsubdyn(subdyn)
self._subdyn = subdyn self._subdyn = subdyn
def is_leader(self, def is_leader(self,
opt: Option) -> bool: opt: Option,
) -> bool:
"""the option is the leader
"""
leader = self.get_leader() leader = self.get_leader()
return opt == leader or (opt.impl_is_dynsymlinkoption() and opt.opt == leader) return opt == leader or (opt.impl_is_dynsymlinkoption() and opt.opt == leader)
def get_leader(self) -> Option: def get_leader(self) -> Option:
"""get leader
"""
return self._children[1][0] return self._children[1][0]
def get_followers(self) -> Iterator[Option]: def get_followers(self) -> Iterator[Option]:
"""get all followers
"""
for follower in self._children[1][1:]: for follower in self._children[1][1:]:
yield follower yield follower
def in_same_group(self, def in_same_leadership(self,
opt: Option) -> bool: opt: Option,
) -> bool:
"""check if followers are in same leadership
"""
if opt.impl_is_dynsymlinkoption(): if opt.impl_is_dynsymlinkoption():
opt = opt.opt opt = opt.opt
return opt in self._children[1] return opt in self._children[1]
def reset(self, def reset(self, config_bag: 'ConfigBag') -> None:
values: Values, """reset follower value
option_bag: OptionBag) -> None: """
config_bag = option_bag.config_bag.copy() values = config_bag.context.get_values()
config_bag = config_bag.copy()
config_bag.remove_validation() config_bag.remove_validation()
for follower in self.get_followers(): for follower in self.get_followers():
soption_bag = OptionBag(follower, soption_bag = OptionBag(follower,
@ -134,71 +153,60 @@ class Leadership(OptionDescription):
values.reset(soption_bag) values.reset(soption_bag)
def follower_force_store_value(self, def follower_force_store_value(self,
values, value,
value, config_bag: 'ConfigBag',
option_bag, owner,
owner, dyn=None,
dyn=None, ) -> None:
) -> None: """apply force_store_value to follower
settings = option_bag.config_bag.context.get_settings() """
if value: if value:
if dyn is None: if dyn is None:
dyn = self dyn = self
for idx, follower in enumerate(dyn.get_children(option_bag.config_bag)): values = config_bag.context.get_values()
for idx, follower in enumerate(dyn.get_children(config_bag)):
foption_bag = OptionBag(follower, foption_bag = OptionBag(follower,
None, None,
option_bag.config_bag, config_bag,
) )
if 'force_store_value' in foption_bag.properties: if 'force_store_value' not in foption_bag.properties:
if idx == 0: continue
indexes = [None] if idx == 0:
else: indexes = [None]
indexes = range(len(value)) else:
for index in indexes: indexes = range(len(value))
foption_bag_index = OptionBag(follower, for index in indexes:
index, foption_bag_index = OptionBag(follower,
option_bag.config_bag, index,
) config_bag,
values.set_storage_value(foption_bag_index.path, )
index, values.set_storage_value(foption_bag_index.path,
values.getvalue(foption_bag_index), index,
owner, values.get_value(foption_bag_index),
) owner,
)
def pop(self, def pop(self,
values: Values,
index: int, index: int,
option_bag: OptionBag, config_bag: 'ConfigBag',
followers: Optional[List[Option]]=undefined, followers: Optional[List[Option]]=undefined,
) -> None: ) -> None:
"""pop leader value and follower's one
"""
if followers is undefined: if followers is undefined:
# followers are not undefined only in SynDynLeadership # followers are not undefined only in SynDynLeadership
followers = self.get_followers() followers = self.get_followers()
config_bag = option_bag.config_bag.copy() config_bag = config_bag.copy()
config_bag.remove_validation() config_bag.remove_validation()
values = config_bag.context.get_values()
for follower in followers: for follower in followers:
follower_path = follower.impl_getpath()
followerlen = values.get_max_length(follower_path)
soption_bag = OptionBag(follower, soption_bag = OptionBag(follower,
index, index,
config_bag, config_bag,
properties=set(), # do not check force_default_on_freeze or force_metaconfig_on_freeze properties=set(), # do not check force_default_on_freeze
# or force_metaconfig_on_freeze
) )
is_default = values.is_default_owner(soption_bag, values.reduce_index(soption_bag)
validate_meta=False,
)
if not is_default and followerlen > index:
values.resetvalue_index(follower_path,
index,
)
if followerlen > index + 1:
for idx in range(index + 1, followerlen):
if values.hasvalue(follower_path,
idx,
):
values.reduce_index(follower_path,
idx,
)
def reset_cache(self, def reset_cache(self,
path: str, path: str,
@ -221,17 +229,16 @@ class Leadership(OptionDescription):
) -> None: ) -> None:
super().reset_cache(path, super().reset_cache(path,
config_bag, config_bag,
resetted_opts) resetted_opts,
)
leader.reset_cache(leader.impl_getpath(), leader.reset_cache(leader.impl_getpath(),
config_bag, config_bag,
None) None)
for follower in followers: for follower in followers:
spath = follower.impl_getpath() follower.reset_cache(follower.impl_getpath(),
follower.reset_cache(spath,
config_bag, config_bag,
None) None,
# do not reset dependencies option )
# resetted_opts.append(spath)
def impl_is_leadership(self) -> None: def impl_is_leadership(self) -> None:
return True return True

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""MACOption
"""
import re import re
from ..i18n import _ from ..i18n import _
@ -25,7 +27,8 @@ from .stroption import RegexpOption
class MACOption(RegexpOption): class MACOption(RegexpOption):
"""represents the choice of a mac address
"""
__slots__ = tuple() __slots__ = tuple()
_regexp = re.compile(r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$") _regexp = re.compile(r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$")
_type = 'macaddress' _type = _('mac address')
_display_name = _('mac address')

View file

@ -18,21 +18,18 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from ipaddress import ip_interface, ip_network """NetmaskOption
from typing import List """
from ipaddress import ip_network
from ..error import ConfigError
from ..setting import undefined, OptionBag, Undefined
from ..i18n import _ from ..i18n import _
from .option import Option
from .stroption import StrOption from .stroption import StrOption
class NetmaskOption(StrOption): class NetmaskOption(StrOption):
"represents the choice of a netmask" """represents the choice of a netmask
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'netmask' _type = _('netmask address')
_display_name = _('netmask address')
def validate(self, def validate(self,
value: str) -> None: value: str) -> None:
@ -41,6 +38,6 @@ class NetmaskOption(StrOption):
if val.startswith("0") and len(val) > 1: if val.startswith("0") and len(val) > 1:
raise ValueError() raise ValueError()
try: try:
ip_network('0.0.0.0/{0}'.format(value)) ip_network(f'0.0.0.0/{value}')
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err

View file

@ -18,7 +18,9 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from ipaddress import ip_address, ip_network """NetworkOption
"""
from ipaddress import ip_network
from ..i18n import _ from ..i18n import _
from .stroption import StrOption from .stroption import StrOption
@ -27,8 +29,7 @@ from .stroption import StrOption
class NetworkOption(StrOption): class NetworkOption(StrOption):
"represents the choice of a network" "represents the choice of a network"
__slots__ = tuple() __slots__ = tuple()
_type = 'network' _type = _('network address')
_display_name = _('network address')
def __init__(self, def __init__(self,
*args, *args,
@ -56,8 +57,8 @@ class NetworkOption(StrOption):
raise ValueError() raise ValueError()
try: try:
ip_network(value) ip_network(value)
except ValueError: except ValueError as err:
raise ValueError() raise ValueError() from err
def second_level_validation(self, def second_level_validation(self,
value: str, value: str,

View file

@ -20,21 +20,19 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import warnings import warnings
import weakref from typing import Any, List, Optional, Dict
from typing import Any, List, Callable, Optional, Dict, Union, Tuple
from itertools import chain from itertools import chain
from .baseoption import BaseOption, submulti, STATIC_TUPLE from .baseoption import BaseOption, submulti
from ..i18n import _ from ..i18n import _
from ..setting import undefined, OptionBag, Undefined from ..setting import undefined, OptionBag
from ..autolib import Calculation, Params, ParamOption, ParamInformation, ParamSelfInformation from ..autolib import Calculation, ParamOption, ParamInformation, ParamSelfInformation
from ..error import (ConfigError, ValueWarning, ValueErrorWarning, from ..error import ValueWarning, ValueErrorWarning, ValueOptionError
ValueOptionError, display_list)
from .syndynoption import SynDynOption from .syndynoption import SynDynOption
#ALLOWED_CONST_LIST = ['_cons_not_equal']
class Option(BaseOption): class Option(BaseOption):
# pylint: disable=too-many-statements,too-many-branches,too-many-arguments,too-many-locals
""" """
Abstract base class for configuration option's. Abstract base class for configuration option's.
@ -55,7 +53,7 @@ class Option(BaseOption):
'_choice_values', '_choice_values',
'_choice_values_params', '_choice_values_params',
) )
_empty = '' _type = None
def __init__(self, def __init__(self,
name: str, name: str,
doc: str, doc: str,
@ -99,7 +97,7 @@ class Option(BaseOption):
is_multi=is_multi) is_multi=is_multi)
if validators is not None: if validators is not None:
if __debug__ and not isinstance(validators, list): if __debug__ and not isinstance(validators, list):
raise ValueError(_('validators must be a list of Calculation for "{}"').format(name)) raise ValueError(_(f'validators must be a list of Calculation for "{name}"'))
for validator in validators: for validator in validators:
if __debug__ and not isinstance(validator, Calculation): if __debug__ and not isinstance(validator, Calculation):
raise ValueError(_('validators must be a Calculation for "{}"').format(name)) raise ValueError(_('validators must be a Calculation for "{}"').format(name))
@ -128,19 +126,20 @@ class Option(BaseOption):
) )
try: try:
self.validate(value) self.validate(value)
self.sync_validate_with_option(value, self.validate_with_option(value,
option_bag) option_bag,
loaded=True,
)
except ValueError as err: except ValueError as err:
str_err = str(err) str_err = str(err)
if not str_err: if not str_err:
raise ValueError(_('invalid default_multi value "{0}" ' raise ValueError(_('invalid default_multi value "{0}" '
'for option "{1}"').format(str(value), 'for option "{1}"').format(str(value),
self.impl_get_display_name())) self.impl_get_display_name())
else: ) from err
raise ValueError(_('invalid default_multi value "{0}" ' raise ValueError(_(f'invalid default_multi value "{value}" for option '
'for option "{1}", {2}').format(str(value), f'"{self.impl_get_display_name()}", {str_err}')
self.impl_get_display_name(), ) from err
str_err))
if _multi is submulti: if _multi is submulti:
if not isinstance(default_multi, Calculation): if not isinstance(default_multi, Calculation):
if not isinstance(default_multi, list): if not isinstance(default_multi, list):
@ -158,11 +157,15 @@ class Option(BaseOption):
undefined, undefined,
properties=None, properties=None,
) )
self.sync_impl_validate(default, self.impl_validate(default,
option_bag) option_bag,
self.sync_impl_validate(default, loaded=True,
option_bag, )
check_error=False) self.impl_validate(default,
option_bag,
check_error=False,
loaded=True,
)
self.value_dependencies(default, _dependencies_information) self.value_dependencies(default, _dependencies_information)
if (is_multi and default != []) or \ if (is_multi and default != []) or \
(not is_multi and default is not None): (not is_multi and default is not None):
@ -176,6 +179,8 @@ class Option(BaseOption):
value: Any, value: Any,
_dependencies_information: List[str], _dependencies_information: List[str],
) -> Any: ) -> Any:
"""parse dependancies to add dependencies
"""
if isinstance(value, list): if isinstance(value, list):
for val in value: for val in value:
if isinstance(value, list): if isinstance(value, list):
@ -189,8 +194,11 @@ class Option(BaseOption):
value: Any, value: Any,
_dependencies_information: List[str], _dependencies_information: List[str],
) -> Any: ) -> Any:
"""parse dependancy to add dependencies
"""
for param in chain(value.params.args, value.params.kwargs.values()): for param in chain(value.params.args, value.params.kwargs.values()):
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
# pylint: disable=protected-access
param.option._add_dependency(self) param.option._add_dependency(self)
elif isinstance(param, ParamSelfInformation): elif isinstance(param, ParamSelfInformation):
_dependencies_information[1].append(param.information_name) _dependencies_information[1].append(param.information_name)
@ -201,23 +209,28 @@ class Option(BaseOption):
# option's information # option's information
def impl_is_multi(self) -> bool: def impl_is_multi(self) -> bool:
"""is it a multi option
"""
return getattr(self, '_multi', 1) != 1 return getattr(self, '_multi', 1) != 1
def impl_is_submulti(self) -> bool: def impl_is_submulti(self) -> bool:
"""is it a submulti option
"""
return getattr(self, '_multi', 1) == 2 return getattr(self, '_multi', 1) == 2
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
"""is a dynsymlinkoption?
"""
return False return False
def get_type(self) -> str: def get_type(self) -> str:
# _display_name for compatibility with older version than 3.0rc3 """get the type of option
return getattr(self, '_type', self._display_name) """
return self._type
def get_display_type(self) -> str:
return self._display_name
def impl_getdefault(self) -> Any: def impl_getdefault(self) -> Any:
"accessing the default value" """accessing the default value
"""
is_multi = self.impl_is_multi() is_multi = self.impl_is_multi()
default = getattr(self, '_default', undefined) default = getattr(self, '_default', undefined)
if default is undefined: if default is undefined:
@ -225,13 +238,13 @@ class Option(BaseOption):
default = [] default = []
else: else:
default = None default = None
else: elif is_multi and isinstance(default, tuple):
if is_multi and isinstance(default, list): default = list(default)
default = list(default)
return default return default
def impl_getdefault_multi(self) -> Any: def impl_getdefault_multi(self) -> Any:
"accessing the default value for a multi" """accessing the default value for a multi
"""
if self.impl_is_submulti(): if self.impl_is_submulti():
default_value = [] default_value = []
else: else:
@ -239,93 +252,25 @@ class Option(BaseOption):
return getattr(self, '_default_multi', default_value) return getattr(self, '_default_multi', default_value)
def impl_get_extra(self, def impl_get_extra(self,
key: str) -> Any: key: str,
) -> Any:
"""if extra parameters are store get it
"""
extra = getattr(self, '_extra', {}) extra = getattr(self, '_extra', {})
if isinstance(extra, tuple): if isinstance(extra, tuple):
if key in extra[0]: if key in extra[0]:
return extra[1][extra[0].index(key)] return extra[1][extra[0].index(key)]
return None return None
else: return extra.get(key)
return extra.get(key)
#__________________________________________________________________________ #__________________________________________________________________________
# validator # validator
def sync_impl_validate(self,
value: Any,
option_bag: OptionBag,
check_error: bool=True) -> None:
"""
"""
is_warnings_only = getattr(self, '_warnings_only', False)
def do_validation(_value,
_index):
if isinstance(_value, list):
raise ValueError(_('which must not be a list').format(_value,
self.impl_get_display_name()))
if _value is not None:
if check_error:
# option validation
self.validate(_value)
self.sync_validate_with_option(_value,
option_bag)
if ((check_error and not is_warnings_only) or
(not check_error and is_warnings_only)):
try:
self.second_level_validation(_value,
is_warnings_only)
except ValueError as err:
if is_warnings_only:
warnings.warn_explicit(ValueWarning(_value,
self._display_name,
self,
'{0}'.format(err),
_index),
ValueWarning,
self.__class__.__name__, 0)
else:
raise err
try:
err_index = None
if isinstance(value, Calculation):
pass
elif not self.impl_is_multi():
val = value
do_validation(val, None)
elif self.impl_is_submulti():
if not isinstance(value, list):
raise ValueError(_('which must be a list'))
for err_index, lval in enumerate(value):
if isinstance(lval, Calculation):
continue
if not isinstance(lval, list):
raise ValueError(_('which "{}" must be a list of list'
'').format(lval))
for val in lval:
if isinstance(val, Calculation):
continue
do_validation(val,
err_index)
else:
# it's a multi
if not isinstance(value, list):
raise ValueError(_('which must be a list'))
for err_index, val in enumerate(value):
if isinstance(val, Calculation):
continue
do_validation(val,
err_index)
except ValueError as err:
raise ValueOptionError(value,
self._display_name,
option_bag.ori_option,
'{0}'.format(err),
err_index)
def impl_validate(self, def impl_validate(self,
value: Any, value: Any,
option_bag: OptionBag, option_bag: OptionBag,
check_error: bool=True) -> None: check_error: bool=True,
loaded: bool=False,
) -> None:
"""Return True if value is really valid """Return True if value is really valid
If not validate or invalid return it returns False If not validate or invalid return it returns False
""" """
@ -337,7 +282,6 @@ class Option(BaseOption):
not 'validator' in config_bag.properties: not 'validator' in config_bag.properties:
return False return False
def _is_not_unique(value, option_bag): def _is_not_unique(value, option_bag):
# if set(value) has not same length than value # if set(value) has not same length than value
if config_bag is undefined or not check_error or \ if config_bag is undefined or not check_error or \
@ -353,9 +297,11 @@ class Option(BaseOption):
'').format(val)) '').format(val))
def calculation_validator(val, def calculation_validator(val,
_index): _index,
):
for validator in getattr(self, '_validators', []): for validator in getattr(self, '_validators', []):
calc_is_warnings_only = hasattr(validator, 'warnings_only') and validator.warnings_only calc_is_warnings_only = hasattr(validator, 'warnings_only') and \
validator.warnings_only
if ((check_error and not calc_is_warnings_only) or if ((check_error and not calc_is_warnings_only) or
(not check_error and calc_is_warnings_only)): (not check_error and calc_is_warnings_only)):
try: try:
@ -374,26 +320,32 @@ class Option(BaseOption):
**kwargs) **kwargs)
except ValueWarning as warn: except ValueWarning as warn:
warnings.warn_explicit(ValueWarning(val, warnings.warn_explicit(ValueWarning(val,
self._display_name, self.get_type(),
self, self,
'{0}'.format(warn), str(warn),
_index), _index),
ValueWarning, ValueWarning,
self.__class__.__name__, 356) self.__class__.__name__, 319)
def do_validation(_value, def do_validation(_value,
_index): _index,
):
#
if isinstance(_value, list): if isinstance(_value, list):
raise ValueError(_('which must not be a list').format(_value, raise ValueError(_('which must not be a list').format(_value,
self.impl_get_display_name())) self.impl_get_display_name()),
)
if isinstance(_value, Calculation) and config_bag is undefined: if isinstance(_value, Calculation) and config_bag is undefined:
return False return
if _value is not None: if _value is not None:
if check_error: if check_error:
# option validation # option validation
self.validate(_value) self.validate(_value)
self.validate_with_option(_value, self.validate_with_option(_value,
option_bag) option_bag,
loaded=loaded,
)
if ((check_error and not is_warnings_only) or if ((check_error and not is_warnings_only) or
(not check_error and is_warnings_only)): (not check_error and is_warnings_only)):
try: try:
@ -402,16 +354,18 @@ class Option(BaseOption):
except ValueError as err: except ValueError as err:
if is_warnings_only: if is_warnings_only:
warnings.warn_explicit(ValueWarning(_value, warnings.warn_explicit(ValueWarning(_value,
self._display_name, self.get_type(),
self, self,
'{0}'.format(err), str(err),
_index), _index),
ValueWarning, ValueWarning,
self.__class__.__name__, 0) self.__class__.__name__, 0)
else: else:
raise err raise err
calculation_validator(_value, if not loaded:
_index) calculation_validator(_value,
_index,
)
try: try:
val = value val = value
err_index = force_index err_index = force_index
@ -423,11 +377,15 @@ class Option(BaseOption):
raise ValueError(_('which must be a list')) raise ValueError(_('which must be a list'))
for val in value: for val in value:
do_validation(val, do_validation(val,
force_index) force_index,
_is_not_unique(value, option_bag) )
_is_not_unique(value,
option_bag,
)
else: else:
do_validation(val, do_validation(val,
force_index) force_index,
)
elif isinstance(value, Calculation) and config_bag is undefined: elif isinstance(value, Calculation) and config_bag is undefined:
pass pass
elif not isinstance(value, list): elif not isinstance(value, list):
@ -444,73 +402,68 @@ class Option(BaseOption):
err_index) err_index)
_is_not_unique(lval, option_bag) _is_not_unique(lval, option_bag)
else: else:
# FIXME subtimal, not several time is whole=True! # FIXME suboptimal, not several time is whole=True!
for err_index, val in enumerate(value): for err_index, val in enumerate(value):
do_validation(val, do_validation(val,
err_index) err_index,
)
_is_not_unique(value, option_bag) _is_not_unique(value, option_bag)
except ValueError as err: except ValueError as err:
if config_bag is undefined or \ if config_bag is undefined or \
'demoting_error_warning' not in config_bag.properties: 'demoting_error_warning' not in config_bag.properties:
raise ValueOptionError(val, raise ValueOptionError(val,
self._display_name, self.get_type(),
option_bag.ori_option, option_bag.ori_option,
'{0}'.format(err), str(err),
err_index) from err err_index) from err
warnings.warn_explicit(ValueErrorWarning(val, warnings.warn_explicit(ValueErrorWarning(val,
self._display_name, self.get_type(),
option_bag.ori_option, option_bag.ori_option,
'{0}'.format(err), str(err),
err_index), err_index),
ValueErrorWarning, ValueErrorWarning,
self.__class__.__name__, 0) self.__class__.__name__, 0)
return False return False
return True return True
def _validate_calculator(self,
callback: Callable,
callback_params: Optional[Params]=None) -> None:
if callback is None:
return
default_multi = getattr(self, '_default_multi', None)
is_multi = self.impl_is_multi()
default = self.impl_getdefault()
if (not is_multi and (default is not None or default_multi is not None)) or \
(is_multi and (default != [] or default_multi is not None)):
raise ValueError(_('default value not allowed if option "{0}" '
'is calculated').format(self.impl_getname()))
def sync_validate_with_option(self,
value: Any,
option_bag: OptionBag) -> None:
pass
def validate_with_option(self, def validate_with_option(self,
value: Any, value: Any,
option_bag: OptionBag) -> None: option_bag: OptionBag,
pass loaded: bool,
) -> None:
"""validation function with option
"""
def second_level_validation(self, def second_level_validation(self,
value: Any, value: Any,
warnings_only: bool) -> None: warnings_only: bool,
pass ) -> None:
"""less import validation function
"""
def impl_is_leader(self): def impl_is_leader(self):
"""check if option is a leader in a leadership
"""
leadership = self.impl_get_leadership() leadership = self.impl_get_leadership()
if leadership is None: if leadership is None:
return False return False
return leadership.is_leader(self) return leadership.is_leader(self)
def impl_is_follower(self): def impl_is_follower(self):
"""check if option is a leader in a follower
"""
leadership = self.impl_get_leadership() leadership = self.impl_get_leadership()
if leadership is None: if leadership is None:
return False return False
return not leadership.is_leader(self) return not leadership.is_leader(self)
def impl_get_leadership(self): def impl_get_leadership(self):
"""get leadership
"""
leadership = getattr(self, '_leadership', None) leadership = getattr(self, '_leadership', None)
if leadership is None: if leadership is None:
return leadership return leadership
#pylint: disable=not-callable
return leadership() return leadership()
def to_dynoption(self, def to_dynoption(self,
@ -518,8 +471,14 @@ class Option(BaseOption):
suffix: str, suffix: str,
dyn_parent, dyn_parent,
) -> SynDynOption: ) -> SynDynOption:
"""tranforme a dynoption to a syndynoption
"""
return SynDynOption(self, return SynDynOption(self,
rootpath, rootpath,
suffix, suffix,
dyn_parent, dyn_parent,
) )
def validate(self, value: Any):
"""option needs a validate function
"""
raise NotImplementedError()

View file

@ -18,27 +18,31 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from copy import copy """OptionDescription
"""
from typing import Optional, Iterator, Union, List from typing import Optional, Iterator, Union, List
from ..i18n import _ from ..i18n import _
from ..setting import ConfigBag, OptionBag, groups, undefined, owners, Undefined from ..setting import ConfigBag, OptionBag, groups, undefined, owners, Undefined
from .baseoption import BaseOption from .baseoption import BaseOption
from .syndynoptiondescription import SynDynOptionDescription, SynDynLeadership from .syndynoptiondescription import SynDynOptionDescription
from ..error import ConfigError, ConflictError from ..error import ConfigError, ConflictError
class CacheOptionDescription(BaseOption): class CacheOptionDescription(BaseOption):
"""manage cache for option description
"""
__slots__ = ('_cache_force_store_values', __slots__ = ('_cache_force_store_values',
'_cache_dependencies_information', '_cache_dependencies_information',
) )
def impl_already_build_caches(self) -> bool: def impl_already_build_caches(self) -> bool:
"""is a readonly option?
"""
return self.impl_is_readonly() return self.impl_is_readonly()
def _build_cache(self, def _build_cache(self,
path='',
_consistencies=None, _consistencies=None,
_consistencies_id=0, _consistencies_id=0,
currpath: List[str]=None, currpath: List[str]=None,
@ -49,6 +53,7 @@ class CacheOptionDescription(BaseOption):
) -> None: ) -> None:
"""validate options and set option has readonly option """validate options and set option has readonly option
""" """
# pylint: disable=too-many-branches,too-many-arguments
# _consistencies is None only when we start to build cache # _consistencies is None only when we start to build cache
if _consistencies is None: if _consistencies is None:
init = True init = True
@ -65,15 +70,16 @@ class CacheOptionDescription(BaseOption):
# cache already set # cache already set
raise ConfigError(_('option description seems to be part of an other ' raise ConfigError(_('option description seems to be part of an other '
'config')) 'config'))
for option in self.get_children(config_bag=undefined, for option in self.get_children(config_bag=undefined, # pylint: disable=no-member
dyn=False): dyn=False,
):
if __debug__: if __debug__:
cache_option.append(option) cache_option.append(option)
sub_currpath = currpath + [option.impl_getname()] sub_currpath = currpath + [option.impl_getname()]
subpath = '.'.join(sub_currpath) subpath = '.'.join(sub_currpath)
if isinstance(option, OptionDescription): if isinstance(option, OptionDescription):
option._build_cache(subpath, # pylint: disable=protected-access
_consistencies, option._build_cache(_consistencies,
_consistencies_id, _consistencies_id,
sub_currpath, sub_currpath,
cache_option, cache_option,
@ -84,7 +90,6 @@ class CacheOptionDescription(BaseOption):
else: else:
for information in option.get_dependencies_information(): for information in option.get_dependencies_information():
dependencies_information.setdefault(information, []).append(option) dependencies_information.setdefault(information, []).append(option)
is_multi = option.impl_is_multi()
if not option.impl_is_symlinkoption(): if not option.impl_is_symlinkoption():
properties = option.impl_getproperties() properties = option.impl_getproperties()
if 'force_store_value' in properties: if 'force_store_value' in properties:
@ -101,18 +106,21 @@ class CacheOptionDescription(BaseOption):
if option.impl_is_readonly(): if option.impl_is_readonly():
raise ConflictError(_('duplicate option: {0}').format(option)) raise ConflictError(_('duplicate option: {0}').format(option))
if not self.impl_is_readonly() and display_name: if not self.impl_is_readonly() and display_name:
option._display_name_function = display_name option._display_name_function = display_name # pylint: disable=protected-access
option._path = subpath option._path = subpath # pylint: disable=protected-access
option._set_readonly() option._set_readonly() # pylint: disable=protected-access
if init: if init:
self._cache_force_store_values = force_store_values self._cache_force_store_values = force_store_values # pylint: disable=attribute-defined-outside-init
self._cache_dependencies_information = dependencies_information self._cache_dependencies_information = dependencies_information # pylint: disable=attribute-defined-outside-init
self._path = self._name self._path = self._name # pylint: disable=attribute-defined-outside-init,no-member
self._set_readonly() self._set_readonly()
def impl_build_force_store_values(self, def impl_build_force_store_values(self,
config_bag: ConfigBag, config_bag: ConfigBag,
) -> None: ) -> None:
"""set value to force_store_values option
"""
# pylint: disable=too-many-branches
def do_option_bags(option): def do_option_bags(option):
if option.issubdyn(): if option.issubdyn():
dynopt = option.getsubdyn() dynopt = option.getsubdyn()
@ -146,7 +154,7 @@ class CacheOptionDescription(BaseOption):
leader = option.impl_get_leadership().get_leader() leader = option.impl_get_leadership().get_leader()
for leader_option_bag in do_option_bags(leader): for leader_option_bag in do_option_bags(leader):
leader_option_bag.properties = frozenset() leader_option_bag.properties = frozenset()
follower_len = len(values.getvalue(leader_option_bag)) follower_len = len(values.get_value(leader_option_bag))
if option.issubdyn(): if option.issubdyn():
subpath = leader_option_bag.option.rootpath subpath = leader_option_bag.option.rootpath
doption = option.to_dynoption(subpath, doption = option.to_dynoption(subpath,
@ -164,7 +172,7 @@ class CacheOptionDescription(BaseOption):
config_bag, config_bag,
properties=frozenset(), properties=frozenset(),
) )
value = values.getvalue(option_bag) value = values.get_value(option_bag)
if value is None: if value is None:
continue continue
values.set_storage_value(subpath, values.set_storage_value(subpath,
@ -175,7 +183,7 @@ class CacheOptionDescription(BaseOption):
else: else:
for option_bag in do_option_bags(option): for option_bag in do_option_bags(option):
option_bag.properties = frozenset() option_bag.properties = frozenset()
value = values.getvalue(option_bag) value = values.get_value(option_bag)
if value is None: if value is None:
continue continue
if values.hasvalue(option_bag.option.impl_getpath()): if values.hasvalue(option_bag.option.impl_getpath()):
@ -188,6 +196,8 @@ class CacheOptionDescription(BaseOption):
class OptionDescriptionWalk(CacheOptionDescription): class OptionDescriptionWalk(CacheOptionDescription):
"""get child of option description
"""
__slots__ = ('_children',) __slots__ = ('_children',)
def get_child(self, def get_child(self,
@ -195,16 +205,18 @@ class OptionDescriptionWalk(CacheOptionDescription):
config_bag: ConfigBag, config_bag: ConfigBag,
subpath: str, subpath: str,
) -> Union[BaseOption, SynDynOptionDescription]: ) -> Union[BaseOption, SynDynOptionDescription]:
"""get a child
"""
# if not dyn # if not dyn
if name in self._children[0]: if name in self._children[0]: # pylint: disable=no-member
option = self._children[1][self._children[0].index(name)] option = self._children[1][self._children[0].index(name)] # pylint: disable=no-member
if option.issubdyn(): if option.issubdyn():
raise AttributeError(_(f'unknown option "{name}" ' raise AttributeError(_(f'unknown option "{name}" '
"in root optiondescription (it's a dynamic option)" "in root optiondescription (it's a dynamic option)"
)) ))
return option return option
# if dyn # if dyn
for child in self._children[1]: for child in self._children[1]: # pylint: disable=no-member
if not child.impl_is_dynoptiondescription(): if not child.impl_is_dynoptiondescription():
continue continue
cname = child.impl_getname() cname = child.impl_getname()
@ -217,7 +229,7 @@ class OptionDescriptionWalk(CacheOptionDescription):
suffix, suffix,
child, child,
) )
if self.impl_get_group_type() == groups.root: if self.impl_get_group_type() == groups.root: # pylint: disable=no-member
raise AttributeError(_(f'unknown option "{name}" ' raise AttributeError(_(f'unknown option "{name}" '
'in root optiondescription' 'in root optiondescription'
)) ))
@ -229,21 +241,22 @@ class OptionDescriptionWalk(CacheOptionDescription):
config_bag: Union[ConfigBag, Undefined], config_bag: Union[ConfigBag, Undefined],
dyn: bool=True, dyn: bool=True,
) -> Union[BaseOption, SynDynOptionDescription]: ) -> Union[BaseOption, SynDynOptionDescription]:
"""get children
"""
if not dyn or config_bag is undefined or \ if not dyn or config_bag is undefined or \
config_bag.context.get_description() == self: config_bag.context.get_description() == self:
subpath = '' subpath = ''
else: else:
subpath = self.impl_getpath() subpath = self.impl_getpath()
children = [] for child in self._children[1]: # pylint: disable=no-member
for child in self._children[1]:
if dyn and child.impl_is_dynoptiondescription(): if dyn and child.impl_is_dynoptiondescription():
for suffix in child.get_suffixes(config_bag): for suffix in child.get_suffixes(config_bag):
children.append(child.to_dynoption(subpath, yield child.to_dynoption(subpath,
suffix, suffix,
child)) child,
)
else: else:
children.append(child) yield child
return children
def get_children_recursively(self, def get_children_recursively(self,
bytype: Optional[BaseOption], bytype: Optional[BaseOption],
@ -251,6 +264,8 @@ class OptionDescriptionWalk(CacheOptionDescription):
config_bag: ConfigBag, config_bag: ConfigBag,
self_opt: BaseOption=None, self_opt: BaseOption=None,
) -> Iterator[Union[BaseOption, SynDynOptionDescription]]: ) -> Iterator[Union[BaseOption, SynDynOptionDescription]]:
"""get children recursively
"""
if self_opt is None: if self_opt is None:
self_opt = self self_opt = self
for option in self_opt.get_children(config_bag): for option in self_opt.get_children(config_bag):
@ -308,8 +323,8 @@ class OptionDescription(OptionDescriptionWalk):
if dynopt_names: if dynopt_names:
for dynopt in dynopt_names: for dynopt in dynopt_names:
if child != dynopt and child.startswith(dynopt): if child != dynopt and child.startswith(dynopt):
raise ConflictError(_('the option\'s name "{}" start as ' raise ConflictError(_(f'the option\'s name "{child}" start as '
'the dynoptiondescription\'s name "{}"').format(child, dynopt)) f'the dynoptiondescription\'s name "{dynopt}"'))
old = child old = child
self._children = children_ self._children = children_
# the group_type is useful for filtering OptionDescriptions in a config # the group_type is useful for filtering OptionDescriptions in a config
@ -322,17 +337,24 @@ class OptionDescription(OptionDescriptionWalk):
'dynoptiondescription')) 'dynoptiondescription'))
def impl_is_optiondescription(self) -> bool: def impl_is_optiondescription(self) -> bool:
"""the option is an option description
"""
return True return True
def impl_is_dynoptiondescription(self) -> bool: def impl_is_dynoptiondescription(self) -> bool:
"""the option is not dynamic
"""
return False return False
def impl_is_leadership(self) -> bool: def impl_is_leadership(self) -> bool:
"""the option is not a leadership
"""
return False return False
# ____________________________________________________________ # ____________________________________________________________
def impl_set_group_type(self, def impl_set_group_type(self,
group_type: groups.GroupType) -> None: group_type: groups.GroupType,
) -> None:
"""sets a given group object to an OptionDescription """sets a given group object to an OptionDescription
:param group_type: an instance of `GroupType` or `LeadershipGroupType` :param group_type: an instance of `GroupType` or `LeadershipGroupType`
@ -351,16 +373,22 @@ class OptionDescription(OptionDescriptionWalk):
self._group_type = group_type self._group_type = group_type
def impl_get_group_type(self) -> groups.GroupType: def impl_get_group_type(self) -> groups.GroupType:
"""get the group type of option description
"""
return self._group_type return self._group_type
def to_dynoption(self, def to_dynoption(self,
rootpath: str, rootpath: str,
suffix: str, suffix: str,
ori_dyn) -> SynDynOptionDescription: ori_dyn) -> SynDynOptionDescription:
"""get syn dyn option description
"""
return SynDynOptionDescription(self, return SynDynOptionDescription(self,
rootpath, rootpath,
suffix, suffix,
ori_dyn) ori_dyn)
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
"""option is not a dyn symlink option
"""
return False return False

View file

@ -18,15 +18,15 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""PasswordOption
"""
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option
from .stroption import StrOption from .stroption import StrOption
class PasswordOption(StrOption): class PasswordOption(StrOption):
"represents the choice of a password" """represents the choice of a password
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'password' _type = _('password')
_display_name = _('password')

View file

@ -18,12 +18,11 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""PermissionsOption
"""
import re import re
import sys
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option
from .intoption import IntOption from .intoption import IntOption
@ -36,8 +35,7 @@ class PermissionsOption(IntOption):
""" """
__slots__ = tuple() __slots__ = tuple()
perm_re = re.compile(r"^[0-7]{3,4}$") perm_re = re.compile(r"^[0-7]{3,4}$")
_type = 'permissions' _type = _('unix file permissions')
_display_name = _('unix file permissions')
def __init__(self, def __init__(self,
*args, *args,
@ -72,4 +70,4 @@ class PermissionsOption(IntOption):
raise ValueError(_(f'{new} has more right than {old}')) raise ValueError(_(f'{new} has more right than {old}'))
old_digit = new_digit old_digit = new_digit
if str_value == '777': if str_value == '777':
raise ValueError(_(f'too weak')) raise ValueError(_('too weak'))

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""PortOption
"""
import re import re
from ..i18n import _ from ..i18n import _
@ -36,8 +38,7 @@ class PortOption(StrOption):
""" """
__slots__ = tuple() __slots__ = tuple()
port_re = re.compile(r"^[0-9]*$") port_re = re.compile(r"^[0-9]*$")
_type = 'port' _port = _('port')
_display_name = _('port')
def __init__(self, def __init__(self,
*args, *args,
@ -81,7 +82,8 @@ class PortOption(StrOption):
def validate(self, def validate(self,
value: str) -> None: value: str) -> None:
super().validate(value) super().validate(value)
if self.impl_get_extra('_allow_protocol') and (value.startswith('tcp:') or value.startswith('udp:')): if self.impl_get_extra('_allow_protocol') and (value.startswith('tcp:') or
value.startswith('udp:')):
value = [value[4:]] 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(':') value = value.split(':')
@ -100,7 +102,8 @@ class PortOption(StrOption):
def second_level_validation(self, def second_level_validation(self,
value: str, value: str,
warnings_only: bool) -> None: warnings_only: bool) -> None:
if self.impl_get_extra('_allow_protocol') and (value.startswith('tcp:') or value.startswith('udp:')): if self.impl_get_extra('_allow_protocol') and (value.startswith('tcp:') or
value.startswith('udp:')):
value = [value[4:]] value = [value[4:]]
elif ':' in value: elif ':' in value:
value = value.split(':') value = value.split(':')

View file

@ -18,31 +18,40 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import sys """StrOption and RegexpOption
"""
from typing import Any from typing import Any
from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
class StrOption(Option): class StrOption(Option):
"represents the choice of a string" """represents a string
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'string' _type = _('string')
_display_name = _('string')
def validate(self, def validate(self,
value: str) -> None: value: str,
) -> None:
"""validation
"""
if not isinstance(value, str): if not isinstance(value, str):
raise ValueError() raise ValueError()
class RegexpOption(StrOption): class RegexpOption(StrOption):
"""regexp validation, this is base option use to do a custom's one
"""
__slots__ = tuple() __slots__ = tuple()
def validate(self, def validate(self,
value: Any) -> None: value: Any,
) -> None:
# pylint: disable=no-member
"""validation
"""
super().validate(value) super().validate(value)
match = self._regexp.search(value) match = self._regexp.search(value)
if not match: if not match:

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""SymLinkOption link to an other option
"""
from typing import Any from typing import Any
from .baseoption import BaseOption, valid_name from .baseoption import BaseOption, valid_name
from ..error import ConfigError from ..error import ConfigError
@ -25,11 +27,15 @@ from ..i18n import _
class SymLinkOption(BaseOption): class SymLinkOption(BaseOption):
"""SymLinkOption link to an other option
"""
__slots__ = ('_opt',) __slots__ = ('_opt',)
def __init__(self, def __init__(self,
name: str, name: str,
opt: BaseOption) -> None: opt: BaseOption,
) -> None:
# pylint: disable=super-init-not-called
if not valid_name(name): if not valid_name(name):
raise ValueError(_('"{0}" is an invalid name for an option').format(name)) raise ValueError(_('"{0}" is an invalid name for an option').format(name))
if not isinstance(opt, BaseOption) or \ if not isinstance(opt, BaseOption) or \
@ -55,27 +61,36 @@ class SymLinkOption(BaseOption):
'dynoptiondescription')) 'dynoptiondescription'))
def impl_has_dependency(self, def impl_has_dependency(self,
self_is_dep: bool=True) -> bool: self_is_dep: bool=True,
) -> bool:
"""If self_is_dep is True, it has dependency (self._opt), so return True """If self_is_dep is True, it has dependency (self._opt), so return True
if self_is_dep is False, cannot has validation or callback, so return False if self_is_dep is False, cannot has validation or callback, so return False
""" """
return self_is_dep return self_is_dep
def impl_is_symlinkoption(self) -> bool: def impl_is_symlinkoption(self) -> bool:
"""it's a symlinkoption
"""
return True return True
def impl_getopt(self) -> BaseOption: def impl_getopt(self) -> BaseOption:
"""get to linked option
"""
return self._opt return self._opt
def issubdyn(self) -> bool: def issubdyn(self) -> bool:
"""it's not a sub dyn option
"""
return False return False
def impl_is_multi(self) -> bool: def impl_is_multi(self) -> bool:
"""is it a multi?
"""
if self._opt.issubdyn(): if self._opt.issubdyn():
return True return True
return self._opt.impl_is_multi() return self._opt.impl_is_multi()
def impl_is_submulti(self) -> bool: def impl_is_submulti(self) -> bool:
if self._opt.issubdyn() and self._opt.impl_is_multi(): """is it a submulti?
return True """
return self._opt.impl_is_submulti() return self._opt.impl_is_submulti()

View file

@ -18,8 +18,9 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""SynDynOption internal option, it's an instanciate synoption
"""
from typing import Any from typing import Any
from ..setting import undefined, OptionBag
from .baseoption import BaseOption from .baseoption import BaseOption
@ -49,35 +50,40 @@ class SynDynOption:
name, name,
) )
def __eq__(self,
left: BaseOption) -> bool:
if not isinstance(left, SynDynOption):
return False
return self.opt == left.opt and \
self.rootpath == left.rootpath and \
self.suffix == left.suffix
def impl_getname(self) -> str: def impl_getname(self) -> str:
"""get option name
"""
return self.opt.impl_getname() + self.dyn_parent.convert_suffix_to_path(self.suffix) return self.opt.impl_getname() + self.dyn_parent.convert_suffix_to_path(self.suffix)
def impl_get_display_name(self) -> str: def impl_get_display_name(self) -> str:
return self.opt._get_display_name(dyn_name=self.impl_getname(), """get option display name
suffix=self.dyn_parent.convert_suffix_to_path(self.suffix), """
suffix = self.dyn_parent.convert_suffix_to_path(self.suffix)
return self.opt._get_display_name(dyn_name=self.impl_getname(), # pylint: disable=protected-access
suffix=suffix,
) )
def impl_getsuffix(self) -> str: def impl_getsuffix(self) -> str:
"""get suffix
"""
return self.suffix return self.suffix
def impl_getpath(self) -> str: def impl_getpath(self) -> str:
"""get path
"""
path = self.impl_getname() path = self.impl_getname()
if self.rootpath: if self.rootpath:
path = f'{self.rootpath}.{path}' path = f'{self.rootpath}.{path}'
return path return path
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
"""it's a dynsymlinkoption
"""
return True return True
def impl_get_leadership(self): def impl_get_leadership(self): # pylint: disable=inconsistent-return-statements
"""is it a leadership?
"""
leadership = self.opt.impl_get_leadership() leadership = self.opt.impl_get_leadership()
if leadership: if leadership:
rootpath = self.rootpath.rsplit('.', 1)[0] rootpath = self.rootpath.rsplit('.', 1)[0]

View file

@ -18,17 +18,21 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""SynDynOptionDescription and SynDynLeadership internal option
it's an instanciate synoptiondescription
"""
from typing import Optional, Iterator, Any, List from typing import Optional, Iterator, Any, List
from ..i18n import _ from ..i18n import _
from ..setting import ConfigBag, groups, undefined, Settings from ..setting import ConfigBag
from ..value import Values
from .baseoption import BaseOption from .baseoption import BaseOption
from .syndynoption import SynDynOption from .syndynoption import SynDynOption
class SynDynOptionDescription: class SynDynOptionDescription:
"""SynDynOptionDescription internal option, it's an instanciate synoptiondescription
"""
__slots__ = ('opt', __slots__ = ('opt',
'rootpath', 'rootpath',
'_suffix', '_suffix',
@ -53,14 +57,14 @@ class SynDynOptionDescription:
name, name,
) )
def impl_getopt(self) -> BaseOption:
return self.opt
def get_child(self, def get_child(self,
name: str, name: str,
config_bag: ConfigBag, config_bag: ConfigBag,
subpath: str, subpath: str,
) -> BaseOption: ) -> BaseOption:
"""get child by name
"""
# pylint: disable=unused-argument
suffix = self.ori_dyn.convert_suffix_to_path(self._suffix) suffix = self.ori_dyn.convert_suffix_to_path(self._suffix)
if name.endswith(suffix): if name.endswith(suffix):
oname = name[:-len(suffix)] oname = name[:-len(suffix)]
@ -78,15 +82,17 @@ class SynDynOptionDescription:
'').format(name, self.impl_get_display_name())) '').format(name, self.impl_get_display_name()))
def impl_getname(self) -> str: def impl_getname(self) -> str:
"""get name
"""
return self.opt.impl_getname() + self.ori_dyn.convert_suffix_to_path(self._suffix) return self.opt.impl_getname() + self.ori_dyn.convert_suffix_to_path(self._suffix)
def impl_is_dynoptiondescription(self) -> bool:
return True
def get_children(self, def get_children(self,
config_bag: ConfigBag, config_bag: ConfigBag,
dyn: bool=True, dyn: bool=True,
): ):
# pylint: disable=unused-argument
"""get children
"""
subpath = self.impl_getpath() subpath = self.impl_getpath()
children = [] children = []
for child in self.opt.get_children(config_bag): for child in self.opt.get_children(config_bag):
@ -97,6 +103,8 @@ class SynDynOptionDescription:
return children return children
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
"""it's a dynsymlinkoption
"""
return True return True
def get_children_recursively(self, def get_children_recursively(self,
@ -105,6 +113,9 @@ class SynDynOptionDescription:
config_bag: ConfigBag, config_bag: ConfigBag,
self_opt: BaseOption=None, self_opt: BaseOption=None,
) -> BaseOption: ) -> BaseOption:
# pylint: disable=unused-argument
"""get children recursively
"""
for option in self.opt.get_children_recursively(bytype, for option in self.opt.get_children_recursively(bytype,
byname, byname,
config_bag, config_bag,
@ -113,23 +124,33 @@ class SynDynOptionDescription:
yield option yield option
def impl_getpath(self) -> str: def impl_getpath(self) -> str:
"""get path
"""
path = self.impl_getname() path = self.impl_getname()
if self.rootpath: if self.rootpath:
path = f'{self.rootpath}.{path}' path = f'{self.rootpath}.{path}'
return path return path
def impl_get_display_name(self) -> str: def impl_get_display_name(self) -> str:
"""get display name
"""
return self.opt.impl_get_display_name() + str(self._suffix) return self.opt.impl_get_display_name() + str(self._suffix)
class SynDynLeadership(SynDynOptionDescription): class SynDynLeadership(SynDynOptionDescription):
"""SynDynLeadership internal option, it's an instanciate synoptiondescription
"""
def get_leader(self) -> SynDynOption: def get_leader(self) -> SynDynOption:
"""get the leader
"""
return self.opt.get_leader().to_dynoption(self.impl_getpath(), return self.opt.get_leader().to_dynoption(self.impl_getpath(),
self._suffix, self._suffix,
self.ori_dyn, self.ori_dyn,
) )
def get_followers(self) -> Iterator[SynDynOption]: def get_followers(self) -> Iterator[SynDynOption]:
"""get followers
"""
subpath = self.impl_getpath() subpath = self.impl_getpath()
for follower in self.opt.get_followers(): for follower in self.opt.get_followers():
yield follower.to_dynoption(subpath, yield follower.to_dynoption(subpath,
@ -142,6 +163,8 @@ class SynDynLeadership(SynDynOptionDescription):
config_bag: 'ConfigBag', config_bag: 'ConfigBag',
resetted_opts: List[str], resetted_opts: List[str],
) -> None: ) -> None:
"""reset cache
"""
leader = self.get_leader() leader = self.get_leader()
followers = self.get_followers() followers = self.get_followers()
self._reset_cache(path, self._reset_cache(path,
@ -155,23 +178,27 @@ class SynDynLeadership(SynDynOptionDescription):
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
"""pop value for a follower
"""
self.opt.pop(*args, self.opt.pop(*args,
followers=self.get_followers(), followers=self.get_followers(),
**kwargs, **kwargs,
) )
def follower_force_store_value(self, def follower_force_store_value(self,
values,
value, value,
option_bag, config_bag,
owner, owner,
) -> None: ) -> None:
self.opt.follower_force_store_value(values, """force store value for a follower
value, """
option_bag, self.opt.follower_force_store_value(value,
config_bag,
owner, owner,
dyn=self, dyn=self,
) )
def impl_getsuffix(self) -> str: def impl_getsuffix(self) -> str:
"""get suffix
"""
return self._suffix return self._suffix

View file

@ -18,22 +18,25 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""URLOption to check url value
"""
import re import re
from typing import Any, Optional, List, Dict from typing import Any, Optional, List, Dict
from ..setting import undefined, Undefined, OptionBag from ..setting import undefined
from ..i18n import _ from ..i18n import _
from .option import Option, Calculation from .option import Calculation
from .stroption import StrOption from .stroption import StrOption
from .domainnameoption import DomainnameOption from .domainnameoption import DomainnameOption
from .portoption import PortOption from .portoption import PortOption
class URLOption(StrOption): class URLOption(StrOption):
"""URLOption to check url value
"""
__slots__ = tuple() __slots__ = tuple()
path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
_type = 'url' _type = _('URL')
_display_name = _('URL')
def __init__(self, def __init__(self,
name: str, name: str,
@ -53,8 +56,7 @@ class URLOption(StrOption):
allow_wellknown: bool=True, allow_wellknown: bool=True,
allow_registred: bool=True, allow_registred: bool=True,
allow_private: bool=False) -> None: allow_private: bool=False) -> None:
# pylint: disable=too-many-arguments,too-many-locals,redefined-builtin
extra = {'_domainname': DomainnameOption(name, extra = {'_domainname': DomainnameOption(name,
doc, doc,
allow_ip=allow_ip, allow_ip=allow_ip,
@ -80,10 +82,10 @@ class URLOption(StrOption):
def _get_domain_port_files(self, def _get_domain_port_files(self,
value: str) -> (str, str): value: str) -> (str, str):
if value.startswith('http://'): if value.startswith('http://'):
type = 'http' type_ = 'http'
value = value[7:] value = value[7:]
elif value.startswith('https://'): elif value.startswith('https://'):
type = 'https' type_ = 'https'
value = value[8:] value = value[8:]
else: else:
raise ValueError(_('must start with http:// or ' raise ValueError(_('must start with http:// or '
@ -100,7 +102,7 @@ class URLOption(StrOption):
if len(splitted) == 1: if len(splitted) == 1:
domain = splitted[0] domain = splitted[0]
port = {'http': '80', port = {'http': '80',
'https': '443'}[type] 'https': '443'}[type_]
else: else:
domain, port = splitted domain, port = splitted
return domain, port, files return domain, port, files
@ -120,7 +122,7 @@ class URLOption(StrOption):
raise ValueError(_('must ends with a valid resource name')) raise ValueError(_('must ends with a valid resource name'))
def second_level_validation(self, value, warnings_only): def second_level_validation(self, value, warnings_only):
domain, port, files = self._get_domain_port_files(value) domain, port, _ = self._get_domain_port_files(value)
# validate port # validate port
portoption = self.impl_get_extra('_port') portoption = self.impl_get_extra('_port')
portoption.second_level_validation(port, warnings_only) portoption.second_level_validation(port, warnings_only)

View file

@ -18,6 +18,8 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
"""UsernameOption or GroupnameOption to check unix username/group value
"""
import re import re
from ..i18n import _ from ..i18n import _
@ -25,14 +27,16 @@ from .stroption import RegexpOption
class UsernameOption(RegexpOption): class UsernameOption(RegexpOption):
"""UsernameOption to check unix username value
"""
__slots__ = tuple() __slots__ = tuple()
#regexp build with 'man 8 adduser' informations #regexp build with 'man 8 adduser' informations
_regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") _regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
_type = 'username' _type = _('unix username')
_display_name = _('unix username')
class GroupnameOption(UsernameOption): class GroupnameOption(UsernameOption):
"""GroupnameOption to check unix group value
"""
__slots__ = tuple() __slots__ = tuple()
_type = 'groupname' _type = _('unix groupname')
_display_name = _('unix groupname')

View file

@ -15,94 +15,92 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
from typing import Union from typing import Union, Set
from itertools import chain from itertools import chain
from .error import PropertiesOptionError, ConstError, ConfigError, LeadershipError, display_list from .error import PropertiesOptionError, ConstError, ConfigError, LeadershipError
from .i18n import _ from .i18n import _
"""If cache and expire is enable, time before cache is expired. # If cache and expire is enable, time before cache is expired.
This delay start first time value/setting is set in cache, even if # This delay start first time value/setting is set in cache, even if
user access several time to value/setting # user access several time to value/setting
"""
EXPIRATION_TIME = 5 EXPIRATION_TIME = 5
"""List of default properties (you can add new one if needed).
For common properties and personalise properties, if a propery is set for #List of default properties (you can add new one if needed).
an Option and for the Config together, Setting raise a PropertiesOptionError #
#For common properties and personalise properties, if a propery is set for
* Common properties: #an Option and for the Config together, Setting raise a PropertiesOptionError
#
hidden #* Common properties:
option with this property can only get value in read only mode. This #
option is not available in read write mode. #hidden
# option with this property can only get value in read only mode. This
disabled # option is not available in read write mode.
option with this property cannot be set/get #
#disabled
frozen # option with this property cannot be set/get
cannot set value for option with this properties if 'frozen' is set in #
config #frozen
# cannot set value for option with this properties if 'frozen' is set in
* Special property: # config
#
permissive #* Special property:
option with 'permissive' cannot raise PropertiesOptionError for properties #
set in permissive #permissive
config with 'permissive', whole option in this config cannot raise # option with 'permissive' cannot raise PropertiesOptionError for properties
PropertiesOptionError for properties set in permissive # set in permissive
# config with 'permissive', whole option in this config cannot raise
mandatory # PropertiesOptionError for properties set in permissive
should set value for option with this properties if 'mandatory' is set in #
config #mandatory
example: 'a', ['a'], [None] are valid # should set value for option with this properties if 'mandatory' is set in
None, [] are not valid # config
# example: 'a', ['a'], [None] are valid
empty # None, [] are not valid
raise mandatory PropertiesOptionError if multi or leader have empty value #
example: ['a'] is valid #empty
[None] is not valid # raise mandatory PropertiesOptionError if multi or leader have empty value
# example: ['a'] is valid
unique # [None] is not valid
raise ValueError if a value is set twice or more in a multi Option #
#unique
* Special Config properties: # raise ValueError if a value is set twice or more in a multi Option
#
cache #* Special Config properties:
if set, enable cache settings and values #
#cache
expire # if set, enable cache settings and values
if set, settings and values in cache expire after ``expiration_time`` #
#expire
everything_frozen # if set, settings and values in cache expire after ``expiration_time``
whole option in config are frozen (even if option have not frozen #
property) #everything_frozen
# whole option in config are frozen (even if option have not frozen
validator # property)
launch validator set by user in option (this property has no effect #
for internal validator) #validator
# launch validator set by user in option (this property has no effect
warnings # for internal validator)
display warnings during validation #
#warnings
demoting_error_warning # display warnings during validation
all value errors are convert to warning (ValueErrorWarning) #
""" #demoting_error_warning
# all value errors are convert to warning (ValueErrorWarning)
DEFAULT_PROPERTIES = frozenset(['cache', 'validator', 'warnings']) DEFAULT_PROPERTIES = frozenset(['cache', 'validator', 'warnings'])
SPECIAL_PROPERTIES = {'frozen', 'mandatory', 'empty', 'force_store_value'} SPECIAL_PROPERTIES = {'frozen', 'mandatory', 'empty', 'force_store_value'}
"""Config can be in two defaut mode: #Config can be in two defaut mode:
#
read_only #read_only
you can get all variables not disabled but you cannot set any variables # you can get all variables not disabled but you cannot set any variables
if a value has a callback without any value, callback is launch and value # if a value has a callback without any value, callback is launch and value
of this variable can change # of this variable can change
you cannot access to mandatory variable without values # you cannot access to mandatory variable without values
#
read_write #read_write
you can get all variables not disabled and not hidden # you can get all variables not disabled and not hidden
you can set all variables not frozen # you can set all variables not frozen
"""
RO_APPEND = frozenset(['frozen', RO_APPEND = frozenset(['frozen',
'disabled', 'disabled',
'validator', 'validator',
@ -147,7 +145,10 @@ static_set = frozenset()
# ____________________________________________________________ # ____________________________________________________________
class Undefined(object): class Undefined:
"""Object undefined, means that there is not value
"""
# pylint: disable=too-few-public-methods
def __str__(self): # pragma: no cover def __str__(self): # pragma: no cover
return 'Undefined' return 'Undefined'
@ -158,6 +159,8 @@ undefined = Undefined()
class OptionBag: class OptionBag:
"""Object to store information for an option
"""
__slots__ = ('option', # current option __slots__ = ('option', # current option
'path', 'path',
'index', 'index',
@ -167,6 +170,7 @@ class OptionBag:
'apply_requires', # apply requires or not for this option 'apply_requires', # apply requires or not for this option
) )
# pylint: disable=too-many-arguments
def __init__(self, def __init__(self,
option, option,
index, index,
@ -187,42 +191,39 @@ class OptionBag:
self.path = path self.path = path
elif option: elif option:
self.path = option.impl_getpath() self.path = option.impl_getpath()
if '.' not in self.path and option == config_bag.context.get_description(): context = config_bag.context
if '.' not in self.path and option == context.get_description():
self.properties = None self.properties = None
elif properties is undefined: elif properties is undefined:
self.properties = config_bag.context.get_settings().getproperties(self, apply_requires=apply_requires) settings = context.get_settings()
self.properties = settings.getproperties(self,
apply_requires=apply_requires,
)
if properties is not undefined: if properties is not undefined:
self.properties = properties self.properties = properties
def __getattr__(self, key): def __getattr__(self, key):
if key == 'ori_option': if key == 'ori_option':
return self.option return self.option
elif key == 'apply_requires': if key == 'apply_requires':
return True return True
return undefined return None
def __delattr__(self, key):
if key in ['properties', 'permissives']:
try:
super().__delattr__(key)
except AttributeError:
pass
return
raise KeyError(_('cannot delete key "{}" for OptionBag').format(key)) # pragma: no cover
def copy(self): def copy(self):
"""copy OptionBag
"""
option_bag = OptionBag(None, option_bag = OptionBag(None,
None, None,
None, None,
) )
for key in self.__slots__: for key in self.__slots__:
if not hasattr(self, key):
continue
setattr(option_bag, key, getattr(self, key)) setattr(option_bag, key, getattr(self, key))
return option_bag return option_bag
class ConfigBag: class ConfigBag:
"""Object to store information for context
"""
__slots__ = ('context', # link to the current context __slots__ = ('context', # link to the current context
'properties', # properties for current context 'properties', # properties for current context
'true_properties', # properties for current context 'true_properties', # properties for current context
@ -246,30 +247,37 @@ class ConfigBag:
if key == 'true_properties': if key == 'true_properties':
return self.properties return self.properties
if key == 'expiration_time': if key == 'expiration_time':
self.expiration_time = EXPIRATION_TIME self.expiration_time = EXPIRATION_TIME # pylint: disable=attribute-defined-outside-init
return self.expiration_time return self.expiration_time
if key == 'is_unrestraint': if key == 'is_unrestraint':
return False return False
raise KeyError('unknown key "{}" for ConfigBag'.format(key)) # pragma: no cover raise KeyError(f'unknown key "{key}" for ConfigBag') # pragma: no cover
def __setattr__(self, key, value): def nowarnings(self):
super().__setattr__(key, value) """do not warnings
"""
def remove_warnings(self):
self.properties = frozenset(self.properties - {'warnings'}) self.properties = frozenset(self.properties - {'warnings'})
def remove_validation(self): def remove_validation(self):
"""do not validate option
"""
self.properties = frozenset(self.properties - {'validator'}) self.properties = frozenset(self.properties - {'validator'})
def unrestraint(self): def unrestraint(self):
self.is_unrestraint = True """do not restraint access to option
self.true_properties = self.properties """
self.is_unrestraint = True # pylint: disable=attribute-defined-outside-init
self.true_properties = self.properties # pylint: disable=attribute-defined-outside-init
self.properties = frozenset(['cache']) self.properties = frozenset(['cache'])
def set_permissive(self): def set_permissive(self):
"""set permissive
"""
self.properties = frozenset(self.properties | {'permissive'}) self.properties = frozenset(self.properties | {'permissive'})
def copy(self): def copy(self):
"""copy the config
"""
kwargs = {} kwargs = {}
for key in self.__slots__: for key in self.__slots__:
kwargs[key] = getattr(self, key) kwargs[key] = getattr(self, key)
@ -277,7 +285,7 @@ class ConfigBag:
# ____________________________________________________________ # ____________________________________________________________
class _NameSpace(object): class _NameSpace:
"""convenient class that emulates a module """convenient class that emulates a module
and builds constants (that is, unique names) and builds constants (that is, unique names)
when attribute is added, we cannot delete it when attribute is added, we cannot delete it
@ -299,28 +307,27 @@ class _NameSpace(object):
class GroupModule(_NameSpace): class GroupModule(_NameSpace):
"emulates a module to manage unique group (OptionDescription) names" "emulates a module to manage unique group (OptionDescription) names"
# pylint: disable=too-few-public-methods
class GroupType(str): class GroupType(str):
"""allowed normal group (OptionDescription) names """allowed normal group (OptionDescription) names
*normal* means : groups that are not leader *normal* means : groups that are not leader
""" """
pass
class DefaultGroupType(GroupType): class DefaultGroupType(GroupType):
"""groups that are default (typically 'default')""" """groups that are default (typically 'default')"""
pass
class LeadershipGroupType(GroupType): class LeadershipGroupType(GroupType):
"""allowed normal group (OptionDescription) names """allowed normal group (OptionDescription) names
*leadership* means : groups that have the 'leadership' attribute set *leadership* means : groups that have the 'leadership' attribute set
""" """
pass
class RootGroupType(GroupType): class RootGroupType(GroupType):
"""root means this is the root optiondescription of whole config """root means this is the root optiondescription of whole config
""" """
pass
def addgroup(self, name): def addgroup(self, name):
"""add a new group type
"""
setattr(groups, name, groups.GroupType(name)) setattr(groups, name, groups.GroupType(name))
@ -329,14 +336,13 @@ class OwnerModule(_NameSpace):
owners are living in `Config._value_owners` owners are living in `Config._value_owners`
""" """
# pylint: disable=too-few-public-methods
class Owner(str): class Owner(str):
"""allowed owner names """allowed owner names
""" """
pass
class DefaultOwner(Owner): class DefaultOwner(Owner):
"""groups that are default (typically 'default')""" """groups that are default (typically 'default')"""
pass
def addowner(self, name): def addowner(self, name):
""" """
@ -348,41 +354,39 @@ class OwnerModule(_NameSpace):
# ____________________________________________________________ # ____________________________________________________________
# populate groups # populate groups
groups = GroupModule() groups = GroupModule()
"""groups.default
default group set when creating a new optiondescription"""
groups.default = groups.DefaultGroupType('default')
"""groups.leadership # groups.default: default group set when creating a new optiondescription
leadership group is a special optiondescription, all suboptions should groups.default = groups.DefaultGroupType('default') # pylint: disable=attribute-defined-outside-init
be multi option and all values should have same length, to find
leader's option, the optiondescription's name should be same than de
leader's option"""
groups.leadership = groups.LeadershipGroupType('leadership')
""" groups.root # groups.leadership: leadership group is a special optiondescription, all suboptions should
this group is the root optiondescription of whole config""" # be multi option and all values should have same length, to find
groups.root = groups.RootGroupType('root') # leader's option, the optiondescription's name should be same than de
# leader's option"""
groups.leadership = groups.LeadershipGroupType('leadership') # pylint: disable=attribute-defined-outside-init
# groups.root: this group is the root optiondescription of whole config
groups.root = groups.RootGroupType('root') # pylint: disable=attribute-defined-outside-init
# ____________________________________________________________ # ____________________________________________________________
# populate owners with default attributes # populate owners with default attributes
owners = OwnerModule() owners = OwnerModule()
"""default
is the config owner after init time""" # default: is the config owner after init time
owners.default = owners.DefaultOwner('default') owners.default = owners.DefaultOwner('default') # pylint: disable=attribute-defined-outside-init
"""user
is the generic is the generic owner""" # user: is the generic is the generic owner
owners.user = owners.Owner('user') owners.addowner('user')
"""forced
special owner when value is forced""" #forced: special owner when value is forced
owners.forced = owners.Owner('forced') owners.addowner('forced')
forbidden_owners = (owners.default, owners.forced) forbidden_owners = (owners.default, owners.forced) # pylint: disable=no-member
# ____________________________________________________________ # ____________________________________________________________
class Settings(object): class Settings:
"``config.Config()``'s configuration options settings" "``config.Config()``'s configuration options settings"
__slots__ = ('_properties', __slots__ = ('_properties',
'_permissives', '_permissives',
@ -417,33 +421,33 @@ class Settings(object):
def get_context_properties(self, def get_context_properties(self,
cache, cache,
): ):
is_cached, props, validated = cache.getcache(None, """get context properties
None, """
None, is_cached, props, _ = cache.getcache(None,
{}, 'context_props',
{}, expiration=False,
'context_props', )
)
if not is_cached: if not is_cached:
props = self._properties.get(None, {}).get(None, self.default_properties) props = self.get_stored_properties(None,
None,
self.default_properties,
)
cache.setcache(None, cache.setcache(None,
None,
props, props,
{}, type_='properties',
props, )
True)
return props return props
def _getproperties(self, def get_stored_properties(self,
path, path: Union[None, str],
index, index: Union[None, int],
default_properties, default_properties: Set[str],
): ) -> Set[str]:
if path not in self._properties: """Get the properties modified by user for a path or index
ret = frozenset(default_properties) """
else: if path not in self._properties or index not in self._properties[path]:
ret = self._properties[path].get(index, frozenset(default_properties)) return frozenset(default_properties)
return ret return self._properties[path][index]
def getproperties(self, def getproperties(self,
option_bag, option_bag,
@ -451,21 +455,15 @@ class Settings(object):
uncalculated=False, uncalculated=False,
help_property=False, help_property=False,
): ):
"""get properties
""" """
""" # pylint: disable=too-many-branches
option = option_bag.option option = option_bag.option
config_bag = option_bag.config_bag
if option.impl_is_symlinkoption(): if option.impl_is_symlinkoption():
option = option.impl_getopt() option = option.impl_getopt()
path = option.impl_getpath()
index = option_bag.index
if apply_requires and not uncalculated and not help_property: if apply_requires and not uncalculated and not help_property:
cache = config_bag.context.properties_cache cache = option_bag.config_bag.context.properties_cache
is_cached, props, validated = cache.getcache(path, is_cached, props, validated = cache.getcache(option_bag, # pylint: disable=unused-variable
config_bag.expiration_time,
index,
config_bag.properties,
{},
'self_props', 'self_props',
) )
else: else:
@ -473,13 +471,16 @@ class Settings(object):
if not is_cached: if not is_cached:
props = set() props = set()
# if index, get option's properties (without index) too # if index, get option's properties (without index) too
p_props = self._getproperties(path, p_props = self.get_stored_properties(option_bag.path,
None, None,
option.impl_getproperties(), option.impl_getproperties(),
) )
if index is not None: if option_bag.index is not None:
p_props = chain(p_props, p_props = chain(p_props,
self._properties.get(path, {}).get(index, option.impl_getproperties()) self.get_stored_properties(option_bag.path,
option_bag.index,
option.impl_getproperties(),
)
) )
for prop in p_props: for prop in p_props:
if uncalculated or isinstance(prop, str): if uncalculated or isinstance(prop, str):
@ -502,43 +503,29 @@ class Settings(object):
new_prop = (new_prop, new_prop) new_prop = (new_prop, new_prop)
if new_prop is None: if new_prop is None:
continue continue
elif (not help_property and not isinstance(new_prop, str)) or \ if (not help_property and not isinstance(new_prop, str)) or \
(help_property and not isinstance(new_prop, tuple)): (help_property and not isinstance(new_prop, tuple)):
raise ValueError(_('invalid property type {} for {} with {} function').format(type(new_prop), raise ValueError(_('invalid property type {type(new_prop)} for '
option_bag.option.impl_getname(), '{option_bag.option.impl_getname()} with '
prop.function.__name__)) '{prop.function.__name__} function'))
if not option.impl_is_optiondescription() and \ if not option.impl_is_optiondescription() and \
option.impl_is_leader() and \ option.impl_is_leader() and \
new_prop not in ALLOWED_LEADER_PROPERTIES: new_prop not in ALLOWED_LEADER_PROPERTIES:
raise LeadershipError(_('leader cannot have "{}" property').format(new_prop)) raise LeadershipError(_('leader cannot have "{new_prop}" property'))
props.add(new_prop) props.add(new_prop)
props -= self.getpermissives(option_bag) props -= self.getpermissives(option_bag)
if not uncalculated and apply_requires and not config_bag.is_unrestraint and not help_property: if not uncalculated and apply_requires and \
cache.setcache(path, not option_bag.config_bag.is_unrestraint and \
index, not help_property:
cache.setcache(option_bag,
props, props,
props, type_='properties',
config_bag.properties, )
True)
return props return props
def has_properties_index(self,
option_bag):
option = option_bag.option
if option.impl_is_symlinkoption():
option = option.impl_getopt()
path = option.impl_getpath()
p_props = self._properties.get(path, {}).get(None, option.impl_getproperties())
if option_bag.index is not None:
p_props = chain(p_props,
self._properties.get(path, {}).get(option_bag.index, option.impl_getproperties()),
)
for prop in p_props:
if not isinstance(prop, str) and prop.has_index(option_bag.option):
return True
return False
def get_context_permissives(self): def get_context_permissives(self):
"""get context permissives
"""
return self.getpermissives(None) return self.getpermissives(None)
def _getpermissives(self, def _getpermissives(self,
@ -554,6 +541,8 @@ class Settings(object):
def getpermissives(self, def getpermissives(self,
option_bag, option_bag,
): ):
"""get permissive
"""
if option_bag is None: if option_bag is None:
path = None path = None
index = None index = None
@ -576,6 +565,8 @@ class Settings(object):
#____________________________________________________________ #____________________________________________________________
# set methods # set methods
def set_context_properties(self, properties, context): def set_context_properties(self, properties, context):
"""set context properties
"""
self._properties.setdefault(None, {})[None] = properties self._properties.setdefault(None, {})[None] = properties
context.reset_cache(None) context.reset_cache(None)
@ -587,21 +578,16 @@ class Settings(object):
(never save properties if same has option properties) (never save properties if same has option properties)
""" """
opt = option_bag.option opt = option_bag.option
if opt.impl_is_symlinkoption():
raise TypeError(_("can't assign property to the symlinkoption \"{}\""
"").format(opt.impl_get_display_name()))
if not opt.impl_is_optiondescription() and opt.impl_is_leader(): if not opt.impl_is_optiondescription() and opt.impl_is_leader():
not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES
if not_allowed_properties: if not_allowed_properties:
if len(not_allowed_properties) == 1: raise LeadershipError(_('leader cannot have "{list(not_allowed_properties)}" '
raise LeadershipError(_('leader cannot have "{}" property').format(list(not_allowed_properties)[0])) 'property'))
else: if ('force_default_on_freeze' in properties or \
raise LeadershipError(_('leader cannot have {} properties').format(display_list(list(not_allowed_properties), add_quote=True))) 'force_metaconfig_on_freeze' in properties) and 'frozen' not in properties:
if ('force_default_on_freeze' in properties or 'force_metaconfig_on_freeze' in properties) and \ raise LeadershipError(_('a leader ({opt.impl_get_display_name()}) cannot have '
'frozen' not in properties: '"force_default_on_freeze" or '
raise LeadershipError(_('a leader ({0}) cannot have ' '"force_metaconfig_on_freeze" property without "frozen"'))
'"force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"'
'').format(opt.impl_get_display_name()))
self._properties.setdefault(option_bag.path, {})[option_bag.index] = properties self._properties.setdefault(option_bag.path, {})[option_bag.index] = properties
# values too because of follower values could have a PropertiesOptionError has value # values too because of follower values could have a PropertiesOptionError has value
option_bag.config_bag.context.reset_cache(option_bag) option_bag.config_bag.context.reset_cache(option_bag)
@ -610,6 +596,8 @@ class Settings(object):
def set_context_permissives(self, def set_context_permissives(self,
permissives, permissives,
): ):
"""set context permissive
"""
self.setpermissives(None, self.setpermissives(None,
permissives, permissives,
) )
@ -630,10 +618,6 @@ class Settings(object):
if not isinstance(permissives, frozenset): if not isinstance(permissives, frozenset):
raise TypeError(_('permissive must be a frozenset')) raise TypeError(_('permissive must be a frozenset'))
if option_bag is not None: if option_bag is not None:
opt = option_bag.option
if opt and opt.impl_is_symlinkoption():
raise TypeError(_("can't assign permissive to the symlinkoption \"{}\""
"").format(opt.impl_get_display_name()))
path = option_bag.path path = option_bag.path
index = option_bag.index index = option_bag.index
else: else:
@ -670,25 +654,29 @@ class Settings(object):
def reset(self, def reset(self,
bag: Union[OptionBag, ConfigBag], bag: Union[OptionBag, ConfigBag],
): ):
"""reset property
"""
path, index, config_bag, option_bag = \ path, index, config_bag, option_bag = \
self._get_path_index_config_option(bag, self._get_path_index_config_option(bag,
"can't reset properties to " "can't reset properties to "
"the symlinkoption \"{}\"", "the symlinkoption \"{}\"",
) )
if path in self._properties and index in self._properties[path]: if path in self._properties and index in self._properties[path]:
del(self._properties[path][index]) del self._properties[path][index]
config_bag.context.reset_cache(option_bag) config_bag.context.reset_cache(option_bag)
def reset_permissives(self, def reset_permissives(self,
bag: Union[OptionBag, ConfigBag], bag: Union[OptionBag, ConfigBag],
): ):
"""reset permission
"""
path, index, config_bag, option_bag = \ path, index, config_bag, option_bag = \
self._get_path_index_config_option(bag, self._get_path_index_config_option(bag,
"can't reset permissives to " "can't reset permissives to "
"the symlinkoption \"{}\"", "the symlinkoption \"{}\"",
) )
if path in self._permissives and index in self._permissives[path]: if path in self._permissives and index in self._permissives[path]:
del(self._permissives[path][index]) del self._permissives[path][index]
config_bag.context.reset_cache(option_bag) config_bag.context.reset_cache(option_bag)
#____________________________________________________________ #____________________________________________________________
@ -698,6 +686,8 @@ class Settings(object):
apply_requires=True, apply_requires=True,
uncalculated=False, uncalculated=False,
): ):
"""raise if needed
"""
if not uncalculated and apply_requires: if not uncalculated and apply_requires:
option_properties = option_bag.properties option_properties = option_bag.properties
else: else:
@ -707,12 +697,14 @@ class Settings(object):
) )
return self._calc_raises_properties(option_bag.config_bag.properties, return self._calc_raises_properties(option_bag.config_bag.properties,
option_bag.config_bag.permissives, option_bag.config_bag.permissives,
option_properties) option_properties,
)
def _calc_raises_properties(self, def _calc_raises_properties(self,
context_properties, context_properties,
context_permissives, context_permissives,
option_properties): option_properties,
):
raises_properties = context_properties - SPECIAL_PROPERTIES raises_properties = context_properties - SPECIAL_PROPERTIES
# remove global permissive properties # remove global permissive properties
if raises_properties and 'permissive' in raises_properties: if raises_properties and 'permissive' in raises_properties:
@ -725,6 +717,8 @@ class Settings(object):
option_bag, option_bag,
need_help=True, need_help=True,
): ):
"""check properties
"""
config_properties = option_bag.config_bag.properties config_properties = option_bag.config_bag.properties
if not config_properties or config_properties == frozenset(['cache']): if not config_properties or config_properties == frozenset(['cache']):
# if no global property # if no global property
@ -747,49 +741,52 @@ class Settings(object):
raise PropertiesOptionError(option_bag, raise PropertiesOptionError(option_bag,
properties, properties,
self, self,
help_properties=calc_properties) help_properties=calc_properties,
)
def validate_mandatory(self, def validate_mandatory(self,
value, value,
option_bag, option_bag,
): ):
if 'mandatory' in option_bag.config_bag.properties: """verify if option is mandatory without value
values = option_bag.config_bag.context.get_values() """
if option_bag.option.impl_is_follower(): if 'mandatory' not in option_bag.config_bag.properties:
force_allow_empty_list = True return
else: values = option_bag.config_bag.context.get_values()
force_allow_empty_list = False if not ('permissive' in option_bag.config_bag.properties and
if not ('permissive' in option_bag.config_bag.properties and 'mandatory' in option_bag.config_bag.permissives) and \
'mandatory' in option_bag.config_bag.permissives) and \ 'mandatory' in option_bag.properties and values.isempty(option_bag,
'mandatory' in option_bag.properties and values.isempty(option_bag.option, value,
value, False,
force_allow_empty_list=force_allow_empty_list, ):
index=option_bag.index, raise PropertiesOptionError(option_bag,
): ['mandatory'],
raise PropertiesOptionError(option_bag, self,
['mandatory'], )
self, if 'empty' in option_bag.properties and values.isempty(option_bag,
) value,
if 'empty' in option_bag.properties and values.isempty(option_bag.option, True,
value, ):
force_allow_empty_list=True, raise PropertiesOptionError(option_bag,
index=option_bag.index, ['empty'],
): self,
raise PropertiesOptionError(option_bag, )
['empty'],
self,
)
def validate_frozen(self, def validate_frozen(self,
option_bag): option_bag,
):
"""verify if option is frozen
"""
if option_bag.config_bag.properties and \ if option_bag.config_bag.properties and \
('everything_frozen' in option_bag.config_bag.properties or ('everything_frozen' in option_bag.config_bag.properties or
('frozen' in option_bag.config_bag.properties and 'frozen' in option_bag.properties)) and \ ('frozen' in option_bag.config_bag.properties and \
'frozen' in option_bag.properties)) and \
not (('permissive' in option_bag.config_bag.properties) and not (('permissive' in option_bag.config_bag.properties) and
'frozen' in option_bag.config_bag.permissives): 'frozen' in option_bag.config_bag.permissives):
raise PropertiesOptionError(option_bag, raise PropertiesOptionError(option_bag,
['frozen'], ['frozen'],
self) self,
)
return False return False
#____________________________________________________________ #____________________________________________________________
# read only/read write # read only/read write
@ -799,7 +796,10 @@ class Settings(object):
append, append,
config_bag, config_bag,
): ):
props = self._properties.get(None, {}).get(None, self.default_properties) props = self.get_stored_properties(None,
None,
self.default_properties,
)
modified = False modified = False
if remove & props: if remove & props:
props = props - remove props = props - remove

View file

@ -207,7 +207,7 @@ class Requires(object):
else: else:
act = 'hide' act = 'hide'
inv_act = 'show' inv_act = 'show'
if option.get_type() == 'choice': if isinstance(option, ChoiceOption):
require_option = self.tiramisu_web.config.unrestraint.option(option_path) require_option = self.tiramisu_web.config.unrestraint.option(option_path)
values = self.tiramisu_web.get_enum(require_option, values = self.tiramisu_web.get_enum(require_option,
require_option.option.ismulti(), require_option.option.ismulti(),

View file

@ -15,39 +15,37 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
import weakref from typing import Union, Optional, List, Any
from typing import Optional, Any, Callable from .error import ConfigError
from .error import ConfigError, PropertiesOptionError from .setting import owners, undefined, forbidden_owners, OptionBag
from .setting import owners, undefined, forbidden_owners, OptionBag, ConfigBag from .autolib import Calculation
from .autolib import Calculation, carry_out_calculation, Params
from .i18n import _ from .i18n import _
class Values: class Values:
"""The `Config`'s root is indeed in charge of the `Option()`'s values, """This class manage value (default value, stored value or calculated value
but the values are physicaly located here, in `Values`, wich is also It's also responsible of a caching utility.
responsible of a caching utility.
""" """
# pylint: disable=too-many-public-methods
__slots__ = ('_values', __slots__ = ('_values',
'_informations', '_informations',
'__weakref__', '__weakref__',
) )
def __init__(self, def __init__(self,
default_values=None, default_values: Union[None, dict]=None,
): ) -> None:
""" """
Initializes the values's dict. Initializes the values's dict.
:param storage: where values or owners are stored :param default_values: values stored by default for this object
""" """
self._informations = {} self._informations = {}
# set default owner # set default owner
if not default_values: if not default_values:
self._values = {None: {None: [None, owners.user]}} default_values = {None: {None: [None, owners.user]}}
else: self._values = default_values
self._values = default_values
#______________________________________________________________________ #______________________________________________________________________
# get value # get value
@ -61,17 +59,13 @@ class Values:
""" """
# try to retrive value in cache # try to retrive value in cache
setting_properties = option_bag.config_bag.properties setting_properties = option_bag.config_bag.properties
cache = option_bag.config_bag.context._impl_values_cache cache = option_bag.config_bag.context.get_values_cache()
is_cached, value, validated = cache.getcache(option_bag.path, is_cached, value, validated = cache.getcache(option_bag,
option_bag.config_bag.expiration_time, 'values',
option_bag.index,
setting_properties,
option_bag.properties,
'value',
) )
# no cached value so get value # no cached value so get value
if not is_cached: if not is_cached:
value = self.getvalue(option_bag) value = self.get_value(option_bag)
# validates and warns value # validates and warns value
if not validated: if not validated:
validate = option_bag.option.impl_validate(value, validate = option_bag.option.impl_validate(value,
@ -85,12 +79,9 @@ class Values:
) )
# set value to cache # set value to cache
if not is_cached: if not is_cached:
cache.setcache(option_bag.path, cache.setcache(option_bag,
option_bag.index,
value, value,
option_bag.properties, validated=validate,
setting_properties,
validate,
) )
if isinstance(value, list): if isinstance(value, list):
# return a copy, so value cannot be modified # return a copy, so value cannot be modified
@ -98,81 +89,28 @@ class Values:
# and return it # and return it
return value return value
def force_to_metaconfig(self, option_bag): def get_value(self,
# force_metaconfig_on_freeze in config => to metaconfig option_bag: OptionBag,
# force_metaconfig_on_freeze in option + config is kernelconfig => to metaconfig ) -> Any:
settings = option_bag.config_bag.context.get_settings() """actually retrieves the stored value or the default value (value modified by user)
if 'force_metaconfig_on_freeze' in option_bag.properties:
settings = option_bag.config_bag.context.get_settings()
if 'force_metaconfig_on_freeze' in option_bag.option.impl_getproperties() and \
not settings._properties.get(option_bag.path, {}).get(None, frozenset()):
# if force_metaconfig_on_freeze is only in option (not in config)
return option_bag.config_bag.context.impl_type == 'config'
else:
return True
return False
def _do_value_list(self,
value: Any,
option_bag: OptionBag,
):
ret = []
for val in value:
if isinstance(val, (list, tuple)):
ret.append(self._do_value_list(val, option_bag))
elif isinstance(val, Calculation):
ret.append(val.execute(option_bag))
else:
ret.append(val)
return ret
def getvalue(self,
option_bag,
):
"""actually retrieves the value
:param path: the path of the `Option`
:param index: index for a follower `Option`
:returns: value :returns: value
""" """
# get owner and value from store # get owner and value from store
# index allowed only for follower default_value = [undefined, owners.default]
index = option_bag.index value, owner = self._values.get(option_bag.path, {}).get(option_bag.index, default_value)
is_follower = option_bag.option.impl_is_follower()
if index is None or not is_follower:
_index = None
else:
_index = index
value, owner = self._values.get(option_bag.path, {}).get(_index, [undefined, owners.default])
if owner == owners.default or \ if owner == owners.default or \
('frozen' in option_bag.properties and \ ('frozen' in option_bag.properties and \
('force_default_on_freeze' in option_bag.properties or self.force_to_metaconfig(option_bag))): ('force_default_on_freeze' in option_bag.properties or \
value = self.getdefaultvalue(option_bag) self.check_force_to_metaconfig(option_bag))):
else: # the value is a default value
value = self.calc_value(option_bag, value) # get it
value = self.get_default_value(option_bag)
return value return value
def calc_value(self, def get_default_value(self,
option_bag, option_bag: OptionBag,
value, ) -> Any:
reset_cache=True):
if isinstance(value, Calculation):
try:
value = value.execute(option_bag)
except ConfigError as err:
msg = _(f'error when calculating "{option_bag.option.impl_get_display_name()}": {err} : {option_bag.path}')
raise ConfigError(msg) from err
elif isinstance(value, (list, tuple)):
value = self._do_value_list(value, option_bag)
if reset_cache:
self.calculate_reset_cache(option_bag, value)
return value
def getdefaultvalue(self,
option_bag,
):
"""get default value: """get default value:
- get parents config value or - get parents config value or
- get calculated value or - get calculated value or
@ -183,45 +121,84 @@ class Values:
# retrieved value from parent config # retrieved value from parent config
return moption_bag.config_bag.context.get_values().get_cached_value(moption_bag) return moption_bag.config_bag.context.get_values().get_cached_value(moption_bag)
# now try to get default value: # now try to get calculated value:
value = self.calc_value(option_bag, value = self.get_calculated_value(option_bag,
option_bag.option.impl_getdefault(), option_bag.option.impl_getdefault(),
) )
if option_bag.index is not None and isinstance(value, (list, tuple)): if option_bag.index is not None and isinstance(value, (list, tuple)) \
if value and option_bag.option.impl_is_submulti(): and (not option_bag.option.impl_is_submulti() or \
# first index is a list, assume other data are list too not value or isinstance(value[0], list)):
if isinstance(value[0], list): # if index (so slave), must return good value for this index
# if index, must return good value for this index # for submulti, first index is a list, assume other data are list too
if len(value) > option_bag.index: if len(value) > option_bag.index:
value = value[option_bag.index] value = value[option_bag.index]
else: else:
# no value for this index, retrieve default multi value # no value for this index, retrieve default multi value
# default_multi is already a list for submulti # default_multi is already a list for submulti
value = self.calc_value(option_bag, value = self.get_calculated_value(option_bag,
option_bag.option.impl_getdefault_multi()) option_bag.option.impl_getdefault_multi(),
elif option_bag.option.impl_is_multi(): )
# if index, must return good value for this index
if len(value) > option_bag.index:
value = value[option_bag.index]
else:
# no value for this index, retrieve default multi value
# default_multi is already a list for submulti
value = self.calc_value(option_bag,
option_bag.option.impl_getdefault_multi())
return value return value
def calculate_reset_cache(self, def get_calculated_value(self,
option_bag, option_bag,
value): value,
if not 'expire' in option_bag.properties: reset_cache=True,
return ) -> Any:
cache = option_bag.config_bag.context._impl_values_cache """value could be a calculation, in this case do calculation
is_cache, cache_value, validated = cache.getcache(option_bag.path, """
None, if isinstance(value, Calculation):
option_bag.index, try:
option_bag.config_bag.properties, value = value.execute(option_bag)
option_bag.properties, except ConfigError as err:
'value') msg = _(f'error when calculating "{option_bag.option.impl_get_display_name()}": '
f'{err} : {option_bag.path}')
raise ConfigError(msg) from err
elif isinstance(value, list):
# if value is a list, do subcalculation
for idx, val in enumerate(value):
value[idx] = self.get_calculated_value(option_bag,
val,
reset_cache=False,
)
if reset_cache:
self.reset_cache_after_calculation(option_bag,
value,
)
return value
#______________________________________________________________________
def check_force_to_metaconfig(self,
option_bag: OptionBag,
) -> bool:
"""Check if the value must be retrieve from parent metaconfig or not
"""
# force_metaconfig_on_freeze is set to an option and context is a kernelconfig
# => to metaconfig
# force_metaconfig_on_freeze is set *explicitly* to an option and context is a
# kernelmetaconfig => to sub metaconfig
if 'force_metaconfig_on_freeze' in option_bag.properties:
settings = option_bag.config_bag.context.get_settings()
if option_bag.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(option_bag.path,
option_bag.index,
frozenset(),
)
return False
def reset_cache_after_calculation(self,
option_bag,
value,
):
"""if value is modification after calculation, invalid cache
"""
cache = option_bag.config_bag.context.get_values_cache()
is_cache, cache_value, _ = cache.getcache(option_bag,
'values',
expiration=False,
)
if not is_cache or cache_value == value: if not is_cache or cache_value == value:
# calculation return same value as previous value, # calculation return same value as previous value,
# so do not invalidate cache # so do not invalidate cache
@ -232,30 +209,47 @@ class Values:
self._set_force_value_suffix(option_bag) self._set_force_value_suffix(option_bag)
def isempty(self, def isempty(self,
opt, option_bag: OptionBag,
value, value: Any,
force_allow_empty_list=False, force_allow_empty_list: bool,
index=None): ) -> bool:
"convenience method to know if an option is empty" """convenience method to know if an option is empty
empty = opt._empty """
if index in [None, undefined] and opt.impl_is_multi(): if option_bag.index is None and option_bag.option.impl_is_submulti():
isempty = value is None or (isinstance(value, list) and not force_allow_empty_list and value == []) or \ # index is not set
(isinstance(value, list) and None in value) or empty in value isempty = True
for val in value:
isempty = self._isempty_multi(val, force_allow_empty_list)
if isempty:
break
elif (option_bag.index is None or \
(option_bag.index is not None and option_bag.option.impl_is_submulti())) and \
option_bag.option.impl_is_multi():
# it's a single list
isempty = self._isempty_multi(value, force_allow_empty_list)
else: else:
isempty = value is None or value == empty or (opt.impl_is_submulti() and value == []) isempty = value is None or value == ''
return isempty return isempty
def _isempty_multi(self,
value: Any,
force_allow_empty_list: bool,
) -> bool:
return (not force_allow_empty_list and value == []) or None in value or '' in value
#______________________________________________________________________ #______________________________________________________________________
# set value # set value
def set_value(self, def set_value(self,
option_bag, option_bag: OptionBag,
value, value: Any,
): ) -> None:
context = option_bag.config_bag.context """set value to option
"""
owner = self.get_context_owner() owner = self.get_context_owner()
if 'validator' in option_bag.config_bag.properties: if 'validator' in option_bag.config_bag.properties:
self.setvalue_validation(value, self.setvalue_validation(value,
option_bag) option_bag,
)
if isinstance(value, list): if isinstance(value, list):
# copy # copy
@ -265,33 +259,35 @@ class Values:
owner, owner,
) )
setting_properties = option_bag.config_bag.properties setting_properties = option_bag.config_bag.properties
validator = 'validator' in setting_properties and 'demoting_error_warning' not in setting_properties validator = 'validator' in setting_properties and \
'demoting_error_warning' not in setting_properties
if validator: if validator:
cache = option_bag.config_bag.context._impl_values_cache cache = option_bag.config_bag.context.get_values_cache()
cache.setcache(option_bag.path, cache.setcache(option_bag,
option_bag.index,
value, value,
option_bag.properties, validated=validator,
setting_properties, )
validator)
if 'force_store_value' in setting_properties and option_bag.option.impl_is_leader(): if 'force_store_value' in setting_properties and option_bag.option.impl_is_leader():
option_bag.option.impl_get_leadership().follower_force_store_value(self, leader = option_bag.option.impl_get_leadership()
value, leader.follower_force_store_value(value,
option_bag, option_bag.config_bag,
owners.forced, owners.forced,
) )
def setvalue_validation(self, def setvalue_validation(self,
value, value,
option_bag, option_bag,
): ):
"""validate value before set value
"""
settings = option_bag.config_bag.context.get_settings() settings = option_bag.config_bag.context.get_settings()
# First validate properties with this value # First validate properties with this value
opt = option_bag.option opt = option_bag.option
settings.validate_frozen(option_bag) settings.validate_frozen(option_bag)
val = self.calc_value(option_bag, val = self.get_calculated_value(option_bag,
value, value,
False,) False,
)
settings.validate_mandatory(val, settings.validate_mandatory(val,
option_bag, option_bag,
) )
@ -320,25 +316,25 @@ class Values:
) )
self._set_force_value_suffix(option_bag) self._set_force_value_suffix(option_bag)
def reduce_index(self,
path,
index):
self._values[path][index - 1] = self._values[path].pop(index)
def set_storage_value(self, def set_storage_value(self,
path, path,
index, index,
value, value,
owner, owner,
): ):
"""set a value
"""
self._values.setdefault(path, {})[index] = [value, owner] self._values.setdefault(path, {})[index] = [value, owner]
def _set_force_value_suffix(self, def _set_force_value_suffix(self, option_bag: OptionBag) -> None:
option_bag: OptionBag, """ force store value for an option for suffixes
) -> None: """
# pylint: disable=too-many-locals
if 'force_store_value' not in option_bag.config_bag.properties: if 'force_store_value' not in option_bag.config_bag.properties:
return return
for woption in option_bag.option._get_suffixes_dependencies():
for woption in option_bag.option._get_suffixes_dependencies(): # pylint: disable=protected-access
# options from dependencies are weakref
option = woption() option = woption()
force_store_options = [] force_store_options = []
for coption in option.get_children_recursively(None, for coption in option.get_children_recursively(None,
@ -350,10 +346,10 @@ class Values:
if not force_store_options: if not force_store_options:
continue continue
rootpath = option.impl_getpath() rootpath = option.impl_getpath()
settings = option_bag.config_bag.context.get_settings()
for suffix in option.get_suffixes(option_bag.config_bag): for suffix in option.get_suffixes(option_bag.config_bag):
for coption in force_store_options: for coption in force_store_options:
subpaths = [rootpath] + coption.impl_getpath()[len(rootpath) + 1:].split('.')[:-1] subpaths = [rootpath] + \
coption.impl_getpath()[len(rootpath) + 1:].split('.')[:-1]
path_suffix = option.convert_suffix_to_path(suffix) path_suffix = option.convert_suffix_to_path(suffix)
subpath = '.'.join([subp + path_suffix for subp in subpaths]) subpath = '.'.join([subp + path_suffix for subp in subpaths])
doption = coption.to_dynoption(subpath, doption = coption.to_dynoption(subpath,
@ -367,7 +363,7 @@ class Values:
option_bag.config_bag, option_bag.config_bag,
properties=frozenset(), properties=frozenset(),
) )
indexes = range(len(self.getvalue(loption_bag))) indexes = range(len(self.get_value(loption_bag)))
else: else:
indexes = [None] indexes = [None]
for index in indexes: for index in indexes:
@ -375,7 +371,8 @@ class Values:
index, index,
option_bag.config_bag, option_bag.config_bag,
) )
self._values.setdefault(coption_bag.path, {})[index] = [self.getvalue(coption_bag), owners.forced] default_value = [self.get_value(coption_bag), owners.forced]
self._values.setdefault(coption_bag.path, {})[index] = default_value
def _get_modified_parent(self, def _get_modified_parent(self,
option_bag: OptionBag, option_bag: OptionBag,
@ -398,8 +395,9 @@ class Values:
# remove force_metaconfig_on_freeze only if option in metaconfig # remove force_metaconfig_on_freeze only if option in metaconfig
# hasn't force_metaconfig_on_freeze properties # hasn't force_metaconfig_on_freeze properties
ori_properties = doption_bag.properties ori_properties = doption_bag.properties
doption_bag.properties = doption_bag.config_bag.context.get_settings().getproperties(doption_bag) settings = doption_bag.config_bag.context.get_settings()
if not self.force_to_metaconfig(doption_bag): doption_bag.properties = settings.getproperties(doption_bag)
if not self.check_force_to_metaconfig(doption_bag):
doption_bag.properties = ori_properties - {'force_metaconfig_on_freeze'} doption_bag.properties = ori_properties - {'force_metaconfig_on_freeze'}
else: else:
doption_bag.properties = ori_properties doption_bag.properties = ori_properties
@ -416,11 +414,15 @@ class Values:
# owner # owner
def is_default_owner(self, def is_default_owner(self,
option_bag, option_bag: OptionBag,
validate_meta=True): validate_meta: bool=True,
) -> bool:
"""is default owner for an option
"""
return self.getowner(option_bag, return self.getowner(option_bag,
validate_meta=validate_meta, validate_meta=validate_meta,
only_default=True) == owners.default only_default=True,
) == owners.default
def hasvalue(self, def hasvalue(self,
path, path,
@ -432,7 +434,7 @@ class Values:
has_path = path in self._values has_path = path in self._values
if index is None: if index is None:
return has_path return has_path
elif has_path: if has_path:
return index in self._values[path] return index in self._values[path]
return False return False
@ -468,14 +470,18 @@ class Values:
else: else:
owner = owners.default owner = owners.default
else: else:
owner = self._values.get(option_bag.path, {}).get(option_bag.index, [undefined, owners.default])[1] owner = self._values.get(option_bag.path, {}).get(option_bag.index,
if validate_meta is not False and (owner is owners.default or \ [undefined, owners.default],
'frozen' in option_bag.properties and 'force_metaconfig_on_freeze' in option_bag.properties): )[1]
if validate_meta is not False and (owner is owners.default or
'frozen' in option_bag.properties and
'force_metaconfig_on_freeze' in option_bag.properties):
moption_bag = self._get_modified_parent(option_bag) moption_bag = self._get_modified_parent(option_bag)
if moption_bag is not None: if moption_bag is not None:
owner = moption_bag.config_bag.context.get_values().getowner(moption_bag, values = moption_bag.config_bag.context.get_values()
only_default=only_default, owner = values.getowner(moption_bag,
) only_default=only_default,
)
elif 'force_metaconfig_on_freeze' in option_bag.properties: elif 'force_metaconfig_on_freeze' in option_bag.properties:
return owners.default return owners.default
return owner return owner
@ -487,32 +493,28 @@ class Values:
""" """
sets a owner to an option sets a owner to an option
:param opt: the `option.Option` object :param option_bag: the `OptionBag` object
:param owner: a valid owner, that is a `setting.owners.Owner` object :param owner: a valid owner, that is a `setting.owners.Owner` object
""" """
opt = option_bag.option
if opt.impl_is_symlinkoption():
raise ConfigError(_("can't set owner for the symlinkoption \"{}\""
"").format(opt.impl_get_display_name()))
if owner in forbidden_owners: if owner in forbidden_owners:
raise ValueError(_('set owner "{0}" is forbidden').format(str(owner))) raise ValueError(_('set owner "{0}" is forbidden').format(str(owner)))
if not self.hasvalue(option_bag.path, option_bag.index): if not self.hasvalue(option_bag.path, option_bag.index):
raise ConfigError(_('no value for {0} cannot change owner to {1}' raise ConfigError(_(f'no value for {option_bag.path} cannot change owner to {owner}'))
'').format(option_bag.path, owner))
option_bag.config_bag.context.get_settings().validate_frozen(option_bag) option_bag.config_bag.context.get_settings().validate_frozen(option_bag)
self._values[option_bag.path][option_bag.index][1] = owner self._values[option_bag.path][option_bag.index][1] = owner
#______________________________________________________________________ #______________________________________________________________________
# reset # reset
def reset(self, def reset(self, option_bag: OptionBag) -> None:
option_bag): """reset value for an option
"""
context = option_bag.config_bag.context context = option_bag.config_bag.context
hasvalue = self.hasvalue(option_bag.path) hasvalue = self.hasvalue(option_bag.path)
setting_properties = option_bag.config_bag.properties setting_properties = option_bag.config_bag.properties
if hasvalue and 'validator' in option_bag.config_bag.properties: if hasvalue and 'validator' in option_bag.config_bag.properties:
fake_context = context._gen_fake_values() fake_context = context.gen_fake_values()
config_bag = option_bag.config_bag.copy() config_bag = option_bag.config_bag.copy()
config_bag.remove_validation() config_bag.remove_validation()
config_bag.context = fake_context config_bag.context = fake_context
@ -521,16 +523,17 @@ class Values:
fake_value = fake_context.get_values() fake_value = fake_context.get_values()
fake_value.reset(soption_bag) fake_value.reset(soption_bag)
soption_bag.config_bag.properties = option_bag.config_bag.properties soption_bag.config_bag.properties = option_bag.config_bag.properties
value = fake_value.getdefaultvalue(soption_bag) value = fake_value.get_default_value(soption_bag)
fake_value.setvalue_validation(value, fake_value.setvalue_validation(value,
soption_bag) soption_bag,
)
opt = option_bag.option opt = option_bag.option
if opt.impl_is_leader(): if opt.impl_is_leader():
opt.impl_get_leadership().reset(self, opt.impl_get_leadership().reset(option_bag.config_bag)
option_bag)
if hasvalue: if hasvalue:
if 'force_store_value' in option_bag.config_bag.properties and 'force_store_value' in option_bag.properties: if 'force_store_value' in option_bag.config_bag.properties and \
value = self.getdefaultvalue(option_bag) 'force_store_value' in option_bag.properties:
value = self.get_default_value(option_bag)
self._setvalue(option_bag, self._setvalue(option_bag,
value, value,
@ -544,72 +547,89 @@ class Values:
context.reset_cache(option_bag) context.reset_cache(option_bag)
if 'force_store_value' in setting_properties and option_bag.option.impl_is_leader(): if 'force_store_value' in setting_properties and option_bag.option.impl_is_leader():
if value is None: if value is None:
value = self.getdefaultvalue(option_bag) value = self.get_default_value(option_bag)
option_bag.option.impl_get_leadership().follower_force_store_value(self, leader = option_bag.option.impl_get_leadership()
value, leader.follower_force_store_value(value,
option_bag, option_bag.config_bag,
owners.forced) owners.forced,
)
#______________________________________________________________________
# Follower
def get_max_length(self, path): def get_max_length(self, path: str) -> int:
"""get max index for a follower and determine the length of the follower
"""
values = self._values.get(path, {}) values = self._values.get(path, {})
if values: if values:
return max(values) + 1 return max(values) + 1
return 0 return 0
def reset_follower(self, def reset_follower(self,
option_bag): option_bag: OptionBag,
if self.hasvalue(option_bag.path, ) -> None:
index=option_bag.index): """reset value for a follower
context = option_bag.config_bag.context """
setting_properties = option_bag.config_bag.properties if not self.hasvalue(option_bag.path,
if 'validator' in setting_properties: index=option_bag.index,
fake_context = context._gen_fake_values() ):
fake_value = fake_context.get_values() return
config_bag = option_bag.config_bag.copy() context = option_bag.config_bag.context
config_bag.remove_validation() setting_properties = option_bag.config_bag.properties
config_bag.context = fake_context if 'validator' in setting_properties:
soption_bag = option_bag.copy() fake_context = context.gen_fake_values()
soption_bag.config_bag = config_bag fake_value = fake_context.get_values()
fake_value.reset_follower(soption_bag) config_bag = option_bag.config_bag.copy()
value = fake_value.getdefaultvalue(soption_bag) config_bag.remove_validation()
fake_value.setvalue_validation(value, config_bag.context = fake_context
soption_bag) soption_bag = option_bag.copy()
if 'force_store_value' in setting_properties and 'force_store_value' in option_bag.properties: soption_bag.config_bag = config_bag
value = self.getdefaultvalue(option_bag) fake_value.reset_follower(soption_bag)
value = fake_value.get_default_value(soption_bag)
fake_value.setvalue_validation(value,
soption_bag)
if 'force_store_value' in setting_properties and \
'force_store_value' in option_bag.properties:
value = self.get_default_value(option_bag)
self._setvalue(option_bag, self._setvalue(option_bag,
value, value,
owners.forced, owners.forced,
) )
else: else:
self.resetvalue_index(option_bag.path, self.resetvalue_index(option_bag)
option_bag.index, context.reset_cache(option_bag)
)
context.reset_cache(option_bag)
def resetvalue_index(self, def resetvalue_index(self, option_bag: OptionBag) -> None:
path, """reset a value for a follower at an index
index, """
): if option_bag.path in self._values and option_bag.index in self._values[option_bag.path]:
if path in self._values and index in self._values[path]: del self._values[option_bag.path][option_bag.index]
del self._values[path][index]
def reduce_index(self, option_bag: OptionBag) -> None:
"""reduce follower's value from a specified index
"""
self.resetvalue_index(option_bag)
for index in range(option_bag.index + 1, self.get_max_length(option_bag.path)):
if self.hasvalue(option_bag.path,
index,
):
self._values[option_bag.path][index - 1] = self._values[option_bag.path].pop(index)
def reset_leadership(self, def reset_leadership(self,
option_bag: OptionBag, option_bag: OptionBag,
leadership_option_bag: OptionBag, leadership_option_bag: OptionBag,
index: int, index: int,
): ) -> None:
"""reset leadershop from an index
"""
current_value = self.get_cached_value(option_bag) current_value = self.get_cached_value(option_bag)
length = len(current_value) length = len(current_value)
if index >= length: if index >= length:
raise IndexError(_('index {} is greater than the length {} ' raise IndexError(_('index {index} is greater than the length {length} '
'for option "{}"').format(index, 'for option "{option_bag.option.impl_get_display_name()}"'))
length,
option_bag.option.impl_get_display_name()))
current_value.pop(index) current_value.pop(index)
leadership_option_bag.option.pop(self, leadership_option_bag.option.pop(index,
index, option_bag.config_bag,
option_bag,
) )
self.set_value(option_bag, self.set_value(option_bag,
current_value, current_value,
@ -619,7 +639,6 @@ class Values:
# information # information
def set_information(self, def set_information(self,
config_bag,
option_bag, option_bag,
key, key,
value, value,
@ -634,55 +653,62 @@ class Values:
else: else:
path = option_bag.path path = option_bag.path
self._informations.setdefault(path, {})[key] = value self._informations.setdefault(path, {})[key] = value
if path is not None: if path is None:
for option in option_bag.option.get_dependencies_information(itself=True): return
config_bag.context.reset_cache(option_bag) if key in option_bag.option.get_dependencies_information(itself=True):
option_bag.config_bag.context.reset_cache(option_bag)
def get_information(self, def get_information(self,
config_bag,
option_bag, option_bag,
key, name,
default, default,
): ):
"""retrieves one information's item """retrieves one information's item
:param key: the item string (ex: "help") :param name: the item string (ex: "help")
""" """
if option_bag is None: if option_bag is None:
path = None path = None
else: else:
path = option_bag.path path = option_bag.path
try: try:
return self._informations[path][key] return self._informations[path][name]
except KeyError as err: except KeyError as err:
if option_bag: if option_bag:
return option_bag.option.impl_get_information(key, default) return option_bag.option.impl_get_information(name, default)
if default is not undefined: if default is not undefined:
return default return default
raise ValueError(_("information's item not found: {0}").format(key)) raise ValueError(_("information's item not found: {0}").format(name)) from err
def del_information(self, def del_information(self,
key, key: Any,
raises=True, raises: bool=True,
path=None, path: str=None,
): ):
"""delete information for a specified key
"""
if path in self._informations and key in self._informations[path]: if path in self._informations and key in self._informations[path]:
del self._informations[path][key] del self._informations[path][key]
elif raises: elif raises:
raise ValueError(_(f"information's item not found \"{key}\"")) raise ValueError(_(f"information's item not found \"{key}\""))
def list_information(self, def list_information(self,
path=None, path: str=None,
): ) -> List[str]:
"""list all informations keys for a specified path
"""
return list(self._informations.get(path, {}).keys()) return list(self._informations.get(path, {}).keys())
#____________________________________________________________ #____________________________________________________________
# default owner methods # default owner methods
def set_context_owner(self, owner): def set_context_owner(self, owner: str) -> None:
":param owner: sets the default value for owner at the Config level" """set the context owner
"""
if owner in forbidden_owners: if owner in forbidden_owners:
raise ValueError(_('set owner "{0}" is forbidden').format(str(owner))) raise ValueError(_('set owner "{0}" is forbidden').format(str(owner)))
self._values[None][None][1] = owner self._values[None][None][1] = owner
def get_context_owner(self): def get_context_owner(self) -> str:
"""get the context owner
"""
return self._values[None][None][1] return self._values[None][None][1]