From 6805cecfd5ca5a54444756ef42c9c743342797f8 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 11 May 2023 15:44:48 +0200 Subject: [PATCH] reorganize --- tests/auto/test_auto.py | 11 +- tests/test_config.py | 27 +- tests/test_dyn_optiondescription.py | 5 +- tests/test_leadership.py | 94 +++- tests/test_metaconfig.py | 6 +- tests/test_option.py | 50 +- tests/test_option_callback.py | 32 +- tests/test_option_setting.py | 153 +++++- tests/test_option_validator.py | 137 +++-- tests/test_requires.py | 8 +- tests/test_state.py | 44 -- tests/test_submulti.py | 91 +++- tests/test_symlink.py | 93 +++- tiramisu/api.py | 487 ++++++++---------- tiramisu/autolib.py | 127 ++--- tiramisu/cacheobj.py | 122 +++-- tiramisu/config.py | 397 +++++++-------- tiramisu/function.py | 447 ++++++++-------- tiramisu/log.py | 2 +- tiramisu/option/__init__.py | 22 + tiramisu/option/baseoption.py | 105 ++-- tiramisu/option/booloption.py | 14 +- tiramisu/option/broadcastoption.py | 20 +- tiramisu/option/choiceoption.py | 34 +- tiramisu/option/dateoption.py | 12 +- tiramisu/option/domainnameoption.py | 17 +- tiramisu/option/dynoptiondescription.py | 26 +- tiramisu/option/emailoption.py | 7 +- tiramisu/option/filenameoption.py | 9 +- tiramisu/option/floatoption.py | 9 +- tiramisu/option/intoption.py | 9 +- tiramisu/option/ipoption.py | 34 +- tiramisu/option/leadership.py | 189 +++---- tiramisu/option/macoption.py | 7 +- tiramisu/option/netmaskoption.py | 21 +- tiramisu/option/networkoption.py | 11 +- tiramisu/option/option.py | 271 +++++----- tiramisu/option/optiondescription.py | 90 ++-- tiramisu/option/passwordoption.py | 10 +- tiramisu/option/permissionsoption.py | 10 +- tiramisu/option/portoption.py | 11 +- tiramisu/option/stroption.py | 23 +- tiramisu/option/symlinkoption.py | 23 +- tiramisu/option/syndynoption.py | 30 +- tiramisu/option/syndynoptiondescription.py | 53 +- tiramisu/option/urloption.py | 22 +- tiramisu/option/usernameoption.py | 12 +- tiramisu/setting.py | 498 +++++++++--------- tiramisu/todict.py | 2 +- tiramisu/value.py | 562 +++++++++++---------- 50 files changed, 2517 insertions(+), 1979 deletions(-) delete mode 100644 tests/test_state.py diff --git a/tests/auto/test_auto.py b/tests/auto/test_auto.py index 0744417..a492b44 100644 --- a/tests/auto/test_auto.py +++ b/tests/auto/test_auto.py @@ -1179,7 +1179,11 @@ def autocheck_permissive(cfg, mcfg, pathread, pathwrite, confread, confwrite, ** cfg2_ = cfg.config(confread).unrestraint else: 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): 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'])) # 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: # assert cfg.config(confread).unrestraint.option(pathread).permissive.get() == frozenset(['disabled']) diff --git a/tests/test_config.py b/tests/test_config.py index c0f6d6d..9afc3e3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -152,6 +152,16 @@ def test_information_config(): # 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(): od1 = make_description() cfg = Config(od1) @@ -199,6 +209,18 @@ def test_information_option(): # 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(): od1 = make_description() cfg = Config(od1) @@ -390,8 +412,7 @@ def test_config_od_name(config_type): cfg = get_config(cfg, config_type) assert cfg.option('val.i').option.name() == 'i' 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 not list_sessions() @@ -403,7 +424,7 @@ def test_config_od_type(config_type): cfg = Config(o2) cfg = get_config(cfg, config_type) 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() diff --git a/tests/test_dyn_optiondescription.py b/tests/test_dyn_optiondescription.py index 911390c..adefe62 100644 --- a/tests/test_dyn_optiondescription.py +++ b/tests/test_dyn_optiondescription.py @@ -1649,7 +1649,6 @@ def test_invalid_subdynod_dyndescription(): def test_invalid_symlink_dyndescription(): st = StrOption('st', '') st2 = SymLinkOption('st2', st) - st2 with pytest.raises(ConfigError): DynOptionDescription('dod', '', [st, st2], suffixes=Calculation(return_list)) # assert not list_sessions() @@ -1658,7 +1657,6 @@ def test_invalid_symlink_dyndescription(): def test_nocallback_dyndescription(): st = StrOption('st', '') st2 = StrOption('st2', '') - st, st2 with pytest.raises(TypeError): DynOptionDescription('dod', '', [st, st2]) # assert not list_sessions() @@ -1843,10 +1841,9 @@ def test_dyn_leadership_requires(): def test_dyn_leadership_mandatory(): 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") -# 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') 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"})) od1 = OptionDescription(name="nsd", doc="nsd", children=[nsd_zones_all, dyn]) cfg = Config(od1) diff --git a/tests/test_leadership.py b/tests/test_leadership.py index 1e92db9..dc37cd1 100644 --- a/tests/test_leadership.py +++ b/tests/test_leadership.py @@ -6,11 +6,11 @@ import pytest from tiramisu.setting import groups, owners 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 -groups.family = groups.GroupType('family') +groups.addgroup('family') def compare(calculated, expected): @@ -94,10 +94,12 @@ def test_iter_on_groups(): #test StopIteration break result = cfg.option('creole').list('option', - group_type=groups.family) + group_type=groups.family, + ) assert list(result) == [] result = cfg.option('creole.general').list('optiondescription', - group_type=groups.family) + group_type=groups.family, + ) assert list(result) == [] # assert not list_sessions() @@ -218,6 +220,61 @@ def test_groups_is_leader(config_type): # 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(): 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) @@ -436,6 +493,9 @@ def test_groups_with_leader_index_mandatory(config_type): cfg.property.read_write() cfg = get_config(cfg, config_type) 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 with pytest.raises(ConfigError): 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() with pytest.raises(ConfigError): 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() with pytest.raises(ConfigError): cfg.option('od.ip_admin_eth0', 0).option.get() @@ -1011,17 +1071,14 @@ def test_follower_properties(): cfg = Config(od1) 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.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', 1).property.get() == ('aproperty',) # 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', 1).property.get() == ('aproperty',) # 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', 1).property.get() == ('aproperty', 'newproperty1') # 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() assert option.option.get() == ip_admin_eth0 # 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() diff --git a/tests/test_metaconfig.py b/tests/test_metaconfig.py index 6035c94..fb6e053 100644 --- a/tests/test_metaconfig.py +++ b/tests/test_metaconfig.py @@ -835,10 +835,10 @@ def test_meta_properties_requires1(): opt2 = BoolOption('opt2', "") disabled_property = Calculation(calc_value, Params(ParamValue('disabled'), - kwargs={'condition': ParamOption(opt1, todict=True), + kwargs={'condition': ParamOption(opt1), 'expected': ParamValue(False)})) 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]) conf1 = Config(od, name='conf1') conf1.property.read_write() @@ -859,7 +859,7 @@ def test_meta_properties_requires_mandatory(): 'expected': ParamValue('yes'), '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_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]) conf1 = Config(od, name='conf1') conf1.property.read_write() diff --git a/tests/test_option.py b/tests/test_option.py index b620b3b..b017928 100644 --- a/tests/test_option.py +++ b/tests/test_option.py @@ -13,13 +13,17 @@ from tiramisu.i18n import _ try: groups.family except: - groups.family = groups.GroupType('family') + groups.addgroup('family') def a_func(): return None +def display_name(*args): + return 'display_name' + + def test_option_valid_name(): IntOption('test', '') with pytest.raises(ValueError): @@ -76,6 +80,16 @@ def test_option_unknown(): 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(): description = "it's ok" string = 'some informations' @@ -178,7 +192,7 @@ def test_unknown_option(): def test_optiondescription_list(): - groups.notfamily1 = groups.GroupType('notfamily1') + groups.addgroup('notfamily1') i = IntOption('test', '') i2 = IntOption('test', '') od1 = OptionDescription('od', '', [i]) @@ -210,7 +224,7 @@ def test_optiondescription_list(): def test_optiondescription_group(): - groups.notfamily = groups.GroupType('notfamily') + groups.addgroup('notfamily') i = IntOption('test', '') i2 = IntOption('test', '') od1 = OptionDescription('od', '', [i]) @@ -240,7 +254,7 @@ def test_optiondescription_group(): def test_optiondescription_group_redefined(): try: - groups.notfamily = groups.GroupType('notfamily') + groups.addgroup('notfamily') except: pass i = IntOption('test', '') @@ -301,12 +315,6 @@ def test_intoption(): # 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(): i1 = IntOption('test1', 'description', min_number=3) with pytest.raises(AttributeError): @@ -322,3 +330,25 @@ def test_option_unknown_func(): cfg = Config(od) with pytest.raises(ConfigError): 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' diff --git a/tests/test_option_callback.py b/tests/test_option_callback.py index 4842f66..8f326e6 100644 --- a/tests/test_option_callback.py +++ b/tests/test_option_callback.py @@ -145,6 +145,30 @@ def test_hidden_if_in2(config_type): # 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): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -268,7 +292,8 @@ def test_callback(config_type): assert cfg.option('val1').value.get() == 'val' cfg.option('val1').value.set('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() assert cfg.option('val1').value.get() == 'val' # assert not list_sessions() @@ -1543,3 +1568,8 @@ def test_calc_dependencies(config_type): assert dep[0].option.get() == val3 # assert cfg.option('val3').option.dependencies() == [] + + +def test_callback__kwargs_wrong(config_type): + with pytest.raises(ValueError): + Params(kwargs='string') diff --git a/tests/test_option_setting.py b/tests/test_option_setting.py index 027831b..1aaf51c 100644 --- a/tests/test_option_setting.py +++ b/tests/test_option_setting.py @@ -60,23 +60,27 @@ def test_mod_read_only_write(): config2 = Config(od1) assert config.property.getdefault() == {'cache', 'validator', 'warnings'} assert config.property.getdefault('read_only', 'append') == {'frozen', - 'disabled', - 'validator', - 'everything_frozen', - 'mandatory', - 'empty', - 'force_store_value'} + 'disabled', + 'validator', + 'everything_frozen', + 'mandatory', + 'empty', + 'force_store_value', + } assert config.property.getdefault('read_only', 'remove') == {'permissive', - 'hidden'} + 'hidden', + } assert config.property.getdefault('read_write', 'append') == {'frozen', - 'disabled', - 'validator', - 'hidden', - 'force_store_value'} + 'disabled', + 'validator', + 'hidden', + 'force_store_value', + } assert config.property.getdefault('read_write', 'remove') == {'permissive', - 'everything_frozen', - 'mandatory', - 'empty'} + 'everything_frozen', + 'mandatory', + 'empty', + } # config.property.setdefault(frozenset(['cache'])) 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() +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(): gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',)) gcgroup = OptionDescription('gc', '', [gcdummy]) @@ -683,7 +699,7 @@ def test_pprint(): intoption = IntOption('int', 'Test int option', default=0) hidden_property = Calculation(calc_value, Params(ParamValue('hidden'), - kwargs={'condition': ParamOption(intoption, todict=True), + kwargs={'condition': ParamOption(intoption), 'expected_0': ParamValue(2), 'expected_1': ParamValue(3), 'expected_2': ParamValue(4), @@ -691,35 +707,28 @@ def test_pprint(): calc_value_property_help) disabled_property = Calculation(calc_value, Params(ParamValue('disabled'), - kwargs={'condition_0': ParamOption(intoption, todict=True), + kwargs={'condition_0': ParamOption(intoption), 'expected_0': ParamValue(1), - 'condition_1': ParamOption(s2, todict=True), + 'condition_1': ParamOption(s2), 'expected_1': ParamValue('string')}), calc_value_property_help) 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', "") hidden_property = Calculation(calc_value, Params(ParamValue('hidden'), - kwargs={'condition': ParamOption(intoption, todict=True), + kwargs={'condition': ParamOption(intoption), 'expected': ParamValue(1)}), calc_value_property_help) descr2 = OptionDescription("options", "options", [val2], properties=(hidden_property,)) - #descr2 = OptionDescription("options", "", [val2], requires=[{'option': intoption, 'expected': 1, 'action': 'hidden'}]) hidden_property = Calculation(calc_value, Params(ParamValue('hidden'), - kwargs={'condition': ParamOption(stroption, todict=True), + kwargs={'condition': ParamOption(stroption), 'expected': ParamValue('2'), 'reverse_condition': ParamValue(True)}), calc_value_property_help) 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]) cfg = Config(od1) @@ -775,3 +784,95 @@ def test_pprint(): assert str(err) == msg_error.format('option', 'string3', prop, '"hidden"') del err # 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,)) diff --git a/tests/test_option_validator.py b/tests/test_option_validator.py index 1c83ef6..223737d 100644 --- a/tests/test_option_validator.py +++ b/tests/test_option_validator.py @@ -6,7 +6,8 @@ import pytest from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \ 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 from tiramisu.setting import groups from tiramisu.error import ValueErrorWarning, ConfigError, PropertiesOptionError @@ -105,11 +106,13 @@ def test_validator(config_type): assert len(w) == 1 assert str(w[0].message) == msg assert cfg.option('opt2').value.valid() is False + # with warnings.catch_warnings(record=True) as w: cfg.option('opt2').value.get() assert len(w) == 1 assert str(w[0].message) == msg assert cfg.option('opt2').value.valid() is False + # with warnings.catch_warnings(record=True) as w: cfg.option('opt2').value.get() assert len(w) == 1 @@ -118,6 +121,13 @@ def test_validator(config_type): # 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): opt1 = StrOption('opt1', '', validators=[Calculation(return_true, Params((ParamSelfOption(), ParamValue('yes'))))], default='val') 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 if config_type != 'tiramisu-api': 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: cfg.option('opt3').value.set(['val']) @@ -335,7 +352,7 @@ def test_validator_warning(config_type): assert len(w) == 1 if config_type != 'tiramisu-api': 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 pytest.raises(ValueError): @@ -348,9 +365,9 @@ def test_validator_warning(config_type): assert len(w) == 2 if config_type != 'tiramisu-api': 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 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() @@ -419,13 +436,13 @@ def test_validator_warning_leadership(config_type): assert len(w) == 1 if config_type != 'tiramisu-api': 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: cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['val']) if config_type != 'tiramisu-api': 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: 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']) if config_type != 'tiramisu-api': 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: 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']) if config_type != 'tiramisu-api': 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: 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']) if config_type != 'tiramisu-api': 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: assert len(w) == 3 # assert not list_sessions() @@ -494,11 +511,12 @@ def test_validator_dependencies(): def test_validator_ip_netmask(config_type): 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]) cfg_ori = Config(od1) cfg = cfg_ori 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('b').value.set('255.255.255.0') 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): 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]) cfg_ori = Config(od1) 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): a = NetworkOption('a', '') b = NetmaskOption('b', '') - c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))))]) - d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))), warnings_only=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), ParamOption(b))), warnings_only=True)]) od1 = OptionDescription('od', '', [a, b, c, d]) warnings.simplefilter("always", ValueErrorWarning) cfg = Config(od1) @@ -581,8 +599,8 @@ def test_validator_ip_in_network(config_type): def test_validator_ip_in_network_incomplete(config_type): a = NetworkOption('a', '') b = NetmaskOption('b', '') - c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))))]) - d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True), ParamOption(b, todict=True))), warnings_only=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), ParamOption(b))), warnings_only=True)]) od1 = OptionDescription('od', '', [a, b, c, d]) warnings.simplefilter("always", ValueErrorWarning) 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): a = NetworkOption('a', '', cidr=True) - c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) - d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=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))), warnings_only=True)]) od1 = OptionDescription('od', '', [a, c, d]) warnings.simplefilter("always", ValueErrorWarning) 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): a = NetworkOption('a', '', cidr=True) - c = IPOption('c', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))))]) - d = IPOption('d', '', validators=[Calculation(valid_in_network, Params((ParamSelfOption(), ParamOption(a, todict=True))), warnings_only=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))), warnings_only=True)]) od1 = OptionDescription('od', '', [a, c, d]) warnings.simplefilter("always", ValueErrorWarning) 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): 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]) od2 = OptionDescription('od2', '', [od]) cfg_ori = Config(od2) @@ -886,6 +904,39 @@ def test_validator_broadcast(config_type): # 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): warnings.simplefilter("always", ValueErrorWarning) a = NetworkOption('a', '', properties=('mandatory', 'disabled')) @@ -969,7 +1020,7 @@ def test_validator_has_dependency(): def test_validator_warnings_only_more_option(config_type): a = IntOption('a', '') 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]) cfg = Config(od1) cfg = get_config(cfg, config_type) @@ -988,7 +1039,7 @@ def test_validator_warnings_only_more_option(config_type): def test_validator_error_prefix(): 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]) cfg = Config(od1) cfg.option('a').value.set(1) @@ -1006,7 +1057,7 @@ def test_validator_error_prefix(): def test_validator_warnings_only_option(config_type): 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]) cfg_ori = Config(od1) cfg = get_config(cfg_ori, config_type) @@ -1086,7 +1137,7 @@ def test_validator_not_equal_leadership_default(): def test_validator_default_diff(): 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]) cfg = Config(od1) # FIXME cfg = get_config(cfg, config_type) @@ -1107,7 +1158,7 @@ def test_validator_default_diff(): def test_validator_permissive(config_type): 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]) cfg = Config(od1) cfg.property.read_write() @@ -1121,7 +1172,7 @@ def test_validator_permissive(config_type): def test_validator_disabled(config_type): 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]) cfg = Config(od1) cfg.property.read_write() @@ -1133,7 +1184,7 @@ def test_validator_disabled(config_type): def test_consistency_disabled_transitive(config_type): 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]) cfg = Config(od1) cfg.property.read_write() @@ -1145,7 +1196,7 @@ def test_consistency_disabled_transitive(config_type): def test_consistency_double_warnings(config_type): a = IntOption('a', '', 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]) warnings.simplefilter("always", ValueErrorWarning) od1 = OptionDescription('od2', '', [od]) @@ -1179,8 +1230,8 @@ def test_consistency_warnings_error(config_type): a = IntOption('a', '', 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)))) + Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(a))), warnings_only=True), + Calculation(valid_not_equal, Params((ParamSelfOption(), ParamOption(b)))) ]) od1 = OptionDescription('od', '', [a, b, c]) warnings.simplefilter("always", ValueErrorWarning) @@ -1196,7 +1247,7 @@ def test_consistency_warnings_error(config_type): def test_consistency_not_equal_has_dependency(): a = IntOption('a', '') 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]) cfg = Config(od1) 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('b').option.has_dependency(False) is False # 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() diff --git a/tests/test_requires.py b/tests/test_requires.py index 68d31df..c323570 100644 --- a/tests/test_requires.py +++ b/tests/test_requires.py @@ -56,7 +56,7 @@ def test_requires(config_type): a = BoolOption('activate_service', '', True) disabled_property = Calculation(calc_value, Params(ParamValue('disabled'), - kwargs={'condition': ParamOption(a, todict=True), + kwargs={'condition': ParamOption(a), 'expected': ParamValue(False)})) b = IPOption('ip_address_service', '', properties=(disabled_property,)) @@ -81,7 +81,7 @@ def test_requires_inverse(config_type): a = BoolOption('activate_service', '', True) disabled_property = Calculation(calc_value, Params(ParamValue('disabled'), - kwargs={'condition': ParamOption(a, todict=True), + kwargs={'condition': ParamOption(a), 'expected': ParamValue(False), 'reverse_condition': ParamValue(True)})) 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) new_property = Calculation(calc_value, Params(ParamValue('new'), - kwargs={'condition': ParamOption(activate_service, todict=True), + kwargs={'condition': ParamOption(activate_service), 'expected': ParamValue(False)}), calc_value_property_help) activate_service_web = BoolOption('activate_service_web', '', True, properties=(new_property,)) disabled_property = Calculation(calc_value, Params(ParamValue('disabled'), - kwargs={'condition': ParamOption(activate_service_web, notraisepropertyerror=True, todict=True), + kwargs={'condition': ParamOption(activate_service_web, notraisepropertyerror=True), 'expected': ParamValue(False)}), calc_value_property_help) ip_address_service_web = IPOption('ip_address_service_web', '', properties=(disabled_property,)) diff --git a/tests/test_state.py b/tests/test_state.py deleted file mode 100644 index 1548a8e..0000000 --- a/tests/test_state.py +++ /dev/null @@ -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) diff --git a/tests/test_submulti.py b/tests/test_submulti.py index a7235aa..967554e 100644 --- a/tests/test_submulti.py +++ b/tests/test_submulti.py @@ -8,7 +8,7 @@ import warnings from tiramisu.setting import groups, owners from tiramisu import StrOption, IntOption, OptionDescription, submulti, Leadership, Config, \ MetaConfig, undefined, Params, ParamOption, Calculation -from tiramisu.error import LeadershipError +from tiramisu.error import LeadershipError, PropertiesOptionError def return_val(val=None): @@ -48,6 +48,51 @@ def test_submulti(): # 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(): with pytest.raises(ValueError): StrOption('multi2', '', default_multi='yes', multi=submulti) @@ -235,6 +280,50 @@ def test_values_with_leader_and_followers_submulti(): # 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(): 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']) diff --git a/tests/test_symlink.py b/tests/test_symlink.py index 0499346..5937ece 100644 --- a/tests/test_symlink.py +++ b/tests/test_symlink.py @@ -4,10 +4,11 @@ from .autopath import do_autopath do_autopath() 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 from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.setting import groups, owners +from tiramisu.i18n import _ def return_value(): @@ -19,9 +20,14 @@ def test_symlink_option(config_type): boolopt = BoolOption("b", "", default=False) linkopt = SymLinkOption("c", boolopt) od1 = OptionDescription("opt", "", - [linkopt, OptionDescription("s1", "", [boolopt])]) + [linkopt, OptionDescription("s1", "", [boolopt])], + ) cfg = Config(od1) 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 cfg.option("s1.b").value.set(True) cfg.option("s1.b").value.set(False) @@ -36,6 +42,62 @@ def test_symlink_option(config_type): # 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): boolopt = BoolOption("b", "", default=False) linkopt = SymLinkOption("c", boolopt) @@ -66,14 +128,10 @@ def test_symlink_addproperties(): od1 = OptionDescription('opt', '', [boolopt, linkopt]) cfg = Config(od1) cfg.property.read_write() - with pytest.raises(TypeError): + with pytest.raises(ConfigError): cfg.option('c').property.add('new') - try: + with pytest.raises(ConfigError): cfg.option('c').property.reset() - except AssertionError: - pass - else: - raise Exception('must raise') # assert not list_sessions() @@ -94,14 +152,10 @@ def test_symlink_addpermissives(): od1 = OptionDescription('opt', '', [boolopt, linkopt]) cfg = Config(od1) cfg.property.read_write() - with pytest.raises(TypeError): + with pytest.raises(ConfigError): cfg.option('c').permissive.set(frozenset(['new'])) - try: + with pytest.raises(ConfigError): cfg.option('c').permissive.reset() - except AssertionError: - pass - else: - raise Exception('must raise') # assert not list_sessions() @@ -340,3 +394,14 @@ def test_symlink_list(config_type): list_opt.append(opt.option.path()) assert list_opt == ['c', 's1.b'] # 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() diff --git a/tiramisu/api.py b/tiramisu/api.py index 3078d69..730692c 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -14,20 +14,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from inspect import ismethod, getdoc, signature -from time import time -from typing import List, Set, Any, Optional, Callable, Union, Dict +from inspect import getdoc +from typing import List, Set, Any, Optional, Callable, Dict from warnings import catch_warnings, simplefilter from functools import wraps from copy import deepcopy -from .error import ConfigError, LeadershipError, PropertiesOptionError, ValueErrorWarning +from .error import ConfigError, LeadershipError, ValueErrorWarning from .i18n import _ -from .setting import ConfigBag, OptionBag, owners, groups, Undefined, undefined, \ - FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, EXPIRATION_TIME +from .setting import ConfigBag, OptionBag, owners, groups, undefined, \ + FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES from .config import KernelConfig, KernelGroupConfig, KernelMetaConfig, KernelMixConfig -from .option import RegexpOption, OptionDescription +from .option import RegexpOption, OptionDescription, ChoiceOption from .todict import TiramisuDict @@ -42,13 +41,12 @@ class TiramisuHelp: def display(doc=''): if _display: # pragma: no cover print(doc) - options = [] all_modules = dir(self) modules = [] max_len = 0 force = False for module_name in all_modules: - if module_name in ['forcepermissive', 'unrestraint']: + if module_name in ['forcepermissive', 'unrestraint', 'nowarnings']: force = True max_len = max(max_len, len('forcepermissive')) elif module_name != 'help' and not module_name.startswith('_'): @@ -60,8 +58,16 @@ class TiramisuHelp: display() if force: display(_('Settings:')) - display(self._tmpl_help.format('forcepermissive', _('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('forcepermissive', + _('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() if isinstance(self, TiramisuDispatcherOption): doc = _(getdoc(self.__call__)) @@ -81,7 +87,6 @@ class TiramisuHelp: class CommonTiramisu(TiramisuHelp): - _allow_optiondescription = True _validate_properties = True def _get_options_bag(self) -> OptionBag: @@ -98,14 +103,6 @@ class CommonTiramisu(TiramisuHelp): 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): if not isinstance(typ, list): types = [typ] @@ -115,16 +112,17 @@ def option_type(typ): def wrapper(func): @wraps(func) def wrapped(*args, **kwargs): - config_bag = args[0]._config_bag - if args[0]._config_bag.context.impl_type == 'group' and 'group' in types: + self = args[0] + config_bag = self._config_bag + if self._config_bag.context.impl_type == 'group' and 'group' in types: options_bag = [OptionBag(None, None, - args[0]._config_bag, - path=args[0]._path, + self._config_bag, + path=self._path, )] kwargs['is_group'] = True - return func(args[0], options_bag, *args[1:], **kwargs) - options_bag = args[0]._get_options_bag() + return func(self, options_bag, *args[1:], **kwargs) + options_bag = self._get_options_bag() option = options_bag[-1].option if option.impl_is_optiondescription() and 'optiondescription' in types or \ not option.impl_is_optiondescription() and ( @@ -133,24 +131,29 @@ def option_type(typ): 'option' in types or \ option.impl_is_leader() and 'leader' in types or \ option.impl_is_follower() and 'follower' in types or \ - option.get_type() == 'choice' and 'choice' in types)): - if not option.impl_is_optiondescription() and not option.impl_is_symlinkoption() and option.impl_is_follower(): - if 'with_index' in types and args[0]._index is not None: - raise ConfigError(_(f'please do not specify index ({args[0].__class__.__name__}.{func.__name__})')) - if 'with_index' not in types and args[0]._index is None: - raise ConfigError(_(f'please specify index with a follower option ({args[0].__class__.__name__}.{func.__name__})')) - elif args[0]._index is not None: - raise ConfigError(_(f'please specify an index only for follower option ({args[0].__class__.__name__}.{func.__name__})')) - return func(args[0], options_bag, *args[1:], **kwargs) - raise ConfigError(_(f'please specify a valid sub function ({args[0].__class__.__name__}.{func.__name__})')) + 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 'with_index' not in types and 'with_or_without_index' not in types and \ + self._index is not None: + msg = _('please do not specify index ' + f'({self.__class__.__name__}.{func.__name__})') + raise ConfigError(_(msg)) + 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 return wrapped return wrapper class CommonTiramisuOption(CommonTiramisu): - _allow_optiondescription = False - _follower_need_index = True _validate_properties = False def __init__(self, @@ -167,60 +170,14 @@ class CommonTiramisuOption(CommonTiramisu): class _TiramisuOptionWalk: - def _filter(self, - opt, - subconfig, - config_bag, - ): - option_bag = OptionBag(opt, - None, - config_bag, - ) - - def _walk(self, - option, - recursive, - type_, + def _list(self, + root_option_bag, + type, group_type, - config_bag, - subconfig, + recursive, ): - options = [] - for opt in option.get_children(config_bag): - 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 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) options = [] @@ -233,11 +190,18 @@ class _TiramisuOptionWalk: group_type=group_type, ): if isinstance(option_bag, dict): - for opt_bag in option_bag.values(): - options.append(TiramisuOption(opt_bag.path, - opt_bag.index, - self._config_bag, - )) + for opts_bag in option_bag.values(): + if isinstance(opts_bag, OptionBag): + options.append(TiramisuOption(opts_bag.path, + 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: options.append(TiramisuOption(option_bag.path, option_bag.index, @@ -245,82 +209,48 @@ class _TiramisuOptionWalk: )) 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): """Manage option""" - _allow_optiondescription = True - _follower_need_index = False _validate_properties = False - @load_option + @option_type(['optiondescription', 'option', 'with_or_without_index']) def get(self, options_bag: List[OptionBag]): """Get Tiramisu option""" option_bag = options_bag[-1] return option_bag.option - @load_option + @option_type(['optiondescription']) def isleadership(self, options_bag: List[OptionBag]): """Test if option is a leader or a follower""" option_bag = options_bag[-1] return option_bag.option.impl_is_leadership() - @load_option + @option_type(['optiondescription', 'option', 'with_or_without_index']) def doc(self, options_bag: List[OptionBag]): """Get option document""" option_bag = options_bag[-1] 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]): """Get option description""" option_bag = options_bag[-1] return option_bag.option.impl_get_information('doc', None) - @load_option - def name(self, - options_bag: List[OptionBag], - follow_symlink: bool=False, - ) -> str: + @option_type(['optiondescription', 'option', 'symlink', 'with_or_without_index']) + def name(self, options_bag: List[OptionBag]) -> str: """Get option name""" option_bag = options_bag[-1] - if not follow_symlink or \ - 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() + return option_bag.option.impl_getname() - @load_option + @option_type(['optiondescription', 'option', 'with_or_without_index', 'symlink']) def path(self, options_bag: List[OptionBag]) -> str: """Get option path""" option_bag = options_bag[-1] return option_bag.path - @load_option + @option_type(['optiondescription', 'option', 'symlink']) def has_dependency(self, options_bag: List[OptionBag], self_is_dep=True, @@ -329,7 +259,7 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption): option_bag = options_bag[-1] return option_bag.option.impl_has_dependency(self_is_dep) - @load_option + @option_type(['optiondescription', 'option']) def dependencies(self, options_bag: List[OptionBag]): """Get dependencies from this option""" option_bag = options_bag[-1] @@ -341,13 +271,13 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption): )) return options - @load_option + @option_type(['optiondescription', 'option', 'with_or_without_index']) def isoptiondescription(self, options_bag: List[OptionBag]): """Test if option is an optiondescription""" option_bag = options_bag[-1] return option_bag.option.impl_is_optiondescription() - @load_option + @option_type(['optiondescription', 'option', 'with_index']) def properties(self, options_bag: List[OptionBag], only_raises=False, @@ -364,7 +294,6 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption): return settings.getproperties(option_bag, apply_requires=False, ) - # do not check cache properties/permissives which are not save (unrestraint, ...) return settings.calc_raises_properties(option_bag, apply_requires=False, uncalculated=uncalculated, @@ -383,55 +312,51 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption): class TiramisuOptionOption(_TiramisuOptionOptionDescription): """Manage option""" - @load_option + @option_type(['option', 'symlink', 'with_or_without_index']) def ismulti(self, options_bag: List[OptionBag]): """Test if option could have multi value""" option_bag = options_bag[-1] return option_bag.option.impl_is_multi() - @load_option + @option_type(['option', 'symlink', 'with_or_without_index']) def issubmulti(self, options_bag: List[OptionBag]): """Test if option could have submulti value""" option_bag = options_bag[-1] return option_bag.option.impl_is_submulti() - @load_option + @option_type(['option', 'with_or_without_index']) def isleader(self, options_bag: List[OptionBag]): """Test if option is a leader""" - option_bag = options_bag[-1] - return option_bag.option.impl_is_leader() + return options_bag[-1].option.impl_is_leader() - @load_option + @option_type(['option', 'with_or_without_index']) def isfollower(self, options_bag: List[OptionBag]): """Test if option is a follower""" - option_bag = options_bag[-1] - return option_bag.option.impl_is_follower() + return options_bag[-1].option.impl_is_follower() - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def isdynamic(self, options_bag: List[OptionBag]): """Test if option is a dynamic optiondescription""" - option_bag = options_bag[-1] - return option_bag.option.impl_is_dynsymlinkoption() + return options_bag[-1].option.impl_is_dynsymlinkoption() - @load_option + @option_type(['option', 'symlink', 'with_or_without_index']) def issymlinkoption(self, options_bag: List[OptionBag]) -> bool: """Test if option is a symlink option""" - option_bag = options_bag[-1] - return option_bag.option.impl_is_symlinkoption() + return options_bag[-1].option.impl_is_symlinkoption() - @load_option + @option_type(['option', 'with_or_without_index', 'symlink']) def default(self, options_bag: List[OptionBag]): """Get default value for an option (not for optiondescription)""" - option_bag = options_bag[-1] - return option_bag.option.impl_getdefault() + return options_bag[-1].option.impl_getdefault() - @load_option + @option_type(['option', 'with_or_without_index', 'symlink']) def defaultmulti(self, options_bag: List[OptionBag]): """Get default value when added a value for a multi option (not for optiondescription)""" - option_bag = options_bag[-1] - return option_bag.option.impl_getdefault_multi() + if not options_bag[-1].option.impl_is_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]): """Get de option type""" option_bag = options_bag[-1] @@ -439,7 +364,7 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription): return 'optiondescription' return option_bag.option.get_type() - @load_option + @option_type('option') def pattern(self, options_bag: List[OptionBag]) -> str: """Get the option pattern""" option_bag = options_bag[-1] @@ -456,15 +381,16 @@ class TiramisuOptionOption(_TiramisuOptionOptionDescription): #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]?)$' - @load_option + @option_type('option') def leader(self, options_bag: List[OptionBag]): """Get the leader option for a follower option""" - option_bag = options_bag[-1] - return TiramisuOption(option_bag.option.impl_get_leadership().get_leader().impl_getpath(), + path = options_bag[-1].option.impl_get_leadership().get_leader().impl_getpath() + return TiramisuOption(path, None, - self._config_bag) + self._config_bag, + ) - @load_option + @option_type(['option', 'with_or_without_index']) def index(self, options_bag: List[OptionBag]): """Get then index of option""" return options_bag[-1].index @@ -474,7 +400,7 @@ class TiramisuOptionOwner(CommonTiramisuOption): #FIXME optiondescription must not have Owner! """Manage option's owner""" - @option_type(['symlink', 'option']) + @option_type(['symlink', 'option', 'with_index']) def get(self, options_bag: List[OptionBag]): """Get owner for a specified option""" option_bag = options_bag[-1] @@ -482,17 +408,15 @@ class TiramisuOptionOwner(CommonTiramisuOption): parent_option_bag = options_bag[-2] else: parent_option_bag = None - return self._config_bag.context.get_owner(option_bag, - parent_option_bag, - ) + return self._config_bag.context.get_owner(option_bag) - @option_type(['symlink', 'option']) + @option_type(['symlink', 'option', 'with_index']) def isdefault(self, options_bag: List[OptionBag]): """Is option has defaut value""" option_bag = options_bag[-1] return self._config_bag.context.get_values().is_default_owner(option_bag) - @option_type('option') + @option_type(['option', 'with_index']) def set(self, options_bag: List[OptionBag], owner: str, @@ -511,11 +435,9 @@ class TiramisuOptionOwner(CommonTiramisuOption): class TiramisuOptionProperty(CommonTiramisuOption): """Manage option's property""" - _allow_optiondescription = True - _follower_need_index = False _validate_properties = False - @load_option + @option_type(['option', 'optiondescription', 'with_index']) def get(self, options_bag: List[OptionBag], only_raises=False, @@ -525,14 +447,13 @@ class TiramisuOptionProperty(CommonTiramisuOption): option_bag = options_bag[-1] if not only_raises: return option_bag.properties - # do not check cache properties/permissives which are not save (unrestraint, ...) settings = self._config_bag.context.get_settings() ret = settings.calc_raises_properties(option_bag, uncalculated=uncalculated, ) return ret - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def add(self, options_bag: List[OptionBag], prop,): @@ -542,15 +463,15 @@ class TiramisuOptionProperty(CommonTiramisuOption): raise ConfigError(_('cannot add this property: "{0}"').format( ' '.join(prop))) settings = self._config_bag.context.get_settings() - props = settings._getproperties(option_bag.path, - option_bag.index, - option_bag.option.impl_getproperties(), - ) + props = settings.get_stored_properties(option_bag.path, + option_bag.index, + option_bag.option.impl_getproperties(), + ) settings.setproperties(option_bag, props | {prop}, ) - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def remove(self, options_bag: List[OptionBag], prop, @@ -562,7 +483,7 @@ class TiramisuOptionProperty(CommonTiramisuOption): props - {prop}, ) - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def reset(self, options_bag: List[OptionBag]): """Reset all personalised properties""" option_bag = options_bag[-1] @@ -571,16 +492,14 @@ class TiramisuOptionProperty(CommonTiramisuOption): class TiramisuOptionPermissive(CommonTiramisuOption): """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]): """Get permissives value""" option_bag = options_bag[-1] return self._config_bag.context.get_settings().getpermissives(option_bag) - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def set(self, options_bag: List[OptionBag], permissives, @@ -591,7 +510,7 @@ class TiramisuOptionPermissive(CommonTiramisuOption): permissives=permissives, ) - @load_option + @option_type(['option', 'optiondescription', 'with_index']) def reset(self, options_bag: List[OptionBag]): """Reset all personalised permissive""" option_bag = options_bag[-1] @@ -600,40 +519,36 @@ class TiramisuOptionPermissive(CommonTiramisuOption): class TiramisuOptionInformation(CommonTiramisuOption): """Manage option's informations""" - _allow_optiondescription = True - _follower_need_index = False - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def get(self, options_bag: List[OptionBag], - key: str, + name: str, default=undefined, ) -> Any: """Get information""" option_bag = options_bag[-1] try: - return self._config_bag.context.get_values().get_information(self._config_bag, - option_bag, - key, + return self._config_bag.context.get_values().get_information(option_bag, + name, undefined, ) 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, options_bag: List[OptionBag], key: str, value: Any) -> None: """Set information""" option_bag = options_bag[-1] - self._config_bag.context.get_values().set_information(self._config_bag, - option_bag, + self._config_bag.context.get_values().set_information(option_bag, key, value, ) - @load_option + @option_type(['option', 'optiondescription']) def reset(self, options_bag: List[OptionBag], key: str, @@ -644,7 +559,7 @@ class TiramisuOptionInformation(CommonTiramisuOption): path=option_bag.path, ) - @load_option + @option_type(['option', 'optiondescription', 'with_or_without_index']) def list(self, options_bag: List[OptionBag], ) -> list: @@ -657,19 +572,14 @@ class TiramisuOptionInformation(CommonTiramisuOption): class TiramisuOptionValue(CommonTiramisuOption): """Manage option's value""" - _allow_optiondescription = True - _follower_need_index = True _validate_properties = True @option_type('optiondescription') def dict(self, options_bag: List[OptionBag]): """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]) - @option_type(['option', 'symlink']) + @option_type(['option', 'symlink', 'with_index']) def get(self, options_bag: List[OptionBag], ): @@ -688,7 +598,7 @@ class TiramisuOptionValue(CommonTiramisuOption): parent_option_bag, ) - @option_type('option') + @option_type(['option', 'with_index']) def set(self, options_bag: List[OptionBag], value, @@ -701,17 +611,18 @@ class TiramisuOptionValue(CommonTiramisuOption): idx = value.index(undefined) soption_bag = option_bag.copy() soption_bag.index = idx - value[idx] = values.getdefaultvalue(soption_bag) + value[idx] = values.get_default_value(soption_bag) elif value == undefined: - value = values.getdefaultvalue(option_bag) - if option_bag.option.impl_is_leader() and len(value) < self._config_bag.context.get_length_leadership(options_bag[-2]): + 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]): raise LeadershipError(_('cannot reduce length of the leader "{}"' '').format(option_bag.option.impl_get_display_name())) return option_bag.config_bag.context.set_value(option_bag, value, ) - @option_type(['group', 'option']) + @option_type(['group', 'option', 'with_index']) def reset(self, options_bag: List[OptionBag], is_group: bool=False, @@ -723,26 +634,18 @@ class TiramisuOptionValue(CommonTiramisuOption): self._config_bag, ) 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]): """Get default value (default of option or calculated value)""" - option_bag = 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) + return self._config_bag.context.get_values().get_default_value(options_bag[-1]) - @option_type('option') + @option_type(['option', 'with_index']) def valid(self, options_bag: List[OptionBag]): """The if the option's value is valid""" option_bag = options_bag[-1] @@ -757,7 +660,7 @@ class TiramisuOptionValue(CommonTiramisuOption): return False return True - @option_type('choice') + @option_type(['choice', 'with_index']) def list(self, options_bag: List[OptionBag]): """All values available for a ChoiceOption""" option_bag = options_bag[-1] @@ -766,19 +669,15 @@ class TiramisuOptionValue(CommonTiramisuOption): @option_type('leader') def pop(self, options_bag: List[OptionBag], - index: int): + index: int, + ): """Pop a value""" - option_bag = options_bag[-1] - if len(options_bag) > 1: - leadership_option_bag = options_bag[-2] - else: - leadership_option_bag = None - self._config_bag.context.pop_leader(option_bag, - leadership_option_bag, - index, - ) + self._config_bag.context.get_values().reset_leadership(options_bag[-1], + options_bag[-2], + index, + ) - @option_type(['leader', 'follower', 'with_index']) + @option_type(['leader', 'follower', 'with_or_without_index']) def len(self, options_bag: List[OptionBag]): """Length for a follower option""" return self._config_bag.context.get_length_leadership(options_bag[-2]) @@ -786,7 +685,7 @@ class TiramisuOptionValue(CommonTiramisuOption): def _registers(_registers: Dict[str, type], prefix: str, - extra_type: Optional[type]=None): + ): for module_name in globals().keys(): if module_name != prefix and module_name.startswith(prefix): module = globals()[module_name] @@ -825,7 +724,7 @@ class TiramisuConfig(TiramisuHelp, _TiramisuOptionWalk): for config_bag in self._orig_config_bags: config_bag.properties = properties config_bag.permissives = permissives - + def name(self): """get the name""" return self._config_bag.context.impl_getname() @@ -893,11 +792,11 @@ class TiramisuOption(CommonTiramisu, TiramisuConfig): group_type=None, ): """List options (by default list only option)""" - return self._list2(options_bag[-1], - type, - group_type, - recursive, - ) + return self._list(options_bag[-1], + type, + group_type, + recursive, + ) def _load_dict(self, clearable: str="all", @@ -943,8 +842,7 @@ class TiramisuContextInformation(TiramisuConfig): """Get an information""" values = self._config_bag.context.get_values() try: - return values.get_information(self._config_bag, - None, + return values.get_information(None, name, undefined, ) @@ -1115,7 +1013,6 @@ class TiramisuContextProperty(TiramisuConfig): def add(self, prop): """Add a config property""" - settings = self._config_bag.context.get_settings() props = set(self.get()) if prop not in props: props.add(prop) @@ -1128,12 +1025,8 @@ class TiramisuContextProperty(TiramisuConfig): props.remove(prop) self._set(frozenset(props)) - def get(self, - default=False): + def get(self): """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 def _set(self, @@ -1168,7 +1061,6 @@ class TiramisuContextProperty(TiramisuConfig): force_store_value = 'force_store_value' not in self._config_bag.properties else: force_store_value = False - settings = self._config_bag.context.get_settings() self._config_bag.context.get_settings()._properties = deepcopy(properties) self._config_bag.context.reset_cache(None, None) self._reset_config_properties() @@ -1205,8 +1097,9 @@ class TiramisuContextProperty(TiramisuConfig): raise ValueError(_('unknown type {}').format(type)) def getdefault(self, - type: Optional[str]=None, - when: Optional[str]=None) -> Set[str]: + type: Optional[str]=None, + when: Optional[str]=None, + ) -> Set[str]: setting = self._config_bag.context.get_settings() if type is None and when is None: return setting.default_properties @@ -1216,15 +1109,12 @@ class TiramisuContextProperty(TiramisuConfig): if type == 'read_only': if when == 'append': return setting.ro_append - else: - return setting.ro_remove - elif type == 'read_write': + return setting.ro_remove + if type == 'read_write': if when == 'append': return setting.rw_append - else: - return setting.rw_remove - else: - raise ValueError(_('unknown type {}').format(type)) + return setting.rw_remove + raise ValueError(_('unknown type {}').format(type)) class TiramisuContextPermissive(TiramisuConfig): @@ -1302,7 +1192,8 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk): ): option = TiramisuOption(path, None, - self._config_bag) + self._config_bag, + ) if first: return option options.append(option) @@ -1318,11 +1209,11 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk): None, self._config_bag, ) - return self._list2(root_option_bag, - type, - group_type, - recursive, - ) + return self._list(root_option_bag, + type, + group_type, + recursive, + ) def _load_dict(self, clearable="all", @@ -1336,10 +1227,12 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk): def dict(self, clearable="all", remotable="minimum", - form=[], + form=None, force=False, ): """Convert config and option to tiramisu format""" + if form is None: + form = [] if force or self._tiramisu_dict is None: self._load_dict(clearable, remotable) return self._tiramisu_dict.todict(form) @@ -1356,7 +1249,6 @@ class _TiramisuContextConfigReset(): def reset(self): """Remove all datas to current config (informations, values, properties, ...)""" # Option's values - settings = self._config_bag.context.get_settings() context_owner = self._config_bag.context.get_values().get_context_owner() self._config_bag.context.get_values()._values = {None: {None: [None, context_owner]}} # Option's informations @@ -1470,6 +1362,7 @@ class _TiramisuContextMixConfig(_TiramisuContextGroupConfig, _TiramisuContextCon def add(self, config): """Add config from MetaConfig""" + # pylint: disable=protected-access self._config_bag.context.add_config(config._config_bag.context) def parents(self): @@ -1495,7 +1388,8 @@ class TiramisuContextCache(TiramisuConfig): self._config_bag.context.reset_cache(None, None) def set_expiration_time(self, - time: int) -> None: + time: int, + ) -> None: """Change expiration time value""" self._config_bag.expiration_time = time @@ -1505,6 +1399,8 @@ class TiramisuContextCache(TiramisuConfig): class TiramisuAPI(TiramisuHelp): + """TiramisuAPI common class + """ _registers = {} def __init__(self, @@ -1520,16 +1416,19 @@ class TiramisuAPI(TiramisuHelp): config_bag = self._config_bag return TiramisuDispatcherOption(config_bag, self._orig_config_bags) - elif subfunc in ['forcepermissive', 'unrestraint']: + if subfunc in ['forcepermissive', 'unrestraint', 'nowarnings']: 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() if subfunc == 'unrestraint': config_bag.unrestraint() + elif subfunc == 'nowarnings': + config_bag.nowarnings() else: config_bag.set_permissive() return TiramisuAPI(config_bag, [self._config_bag]) - elif subfunc == 'config': + if subfunc == 'config': config_type = self._config_bag.context.impl_type if config_type == 'group': config = _TiramisuContextGroupConfig @@ -1541,7 +1440,7 @@ class TiramisuAPI(TiramisuHelp): config = _TiramisuContextConfig return config(self._config_bag, self._orig_config_bags) - elif subfunc in self._registers: + if subfunc in self._registers: config_bag = self._config_bag # del config_bag.permissives return self._registers[subfunc](config_bag, @@ -1549,18 +1448,21 @@ class TiramisuAPI(TiramisuHelp): raise ConfigError(_('please specify a valid sub function ({})').format(subfunc)) def __dir__(self): - return list(self._registers.keys()) + ['unrestraint', 'forcepermissive', 'config'] + return list(self._registers.keys()) + \ + ['unrestraint', 'forcepermissive', 'nowarnings', 'config'] class TiramisuDispatcherOption(TiramisuContextOption): """Select an option""" def __call__(self, path: str, - index: Optional[int]=None) -> TiramisuOption: + index: Optional[int]=None, + ) -> TiramisuOption: """Select an option by path""" return TiramisuOption(path, index, - self._config_bag) + self._config_bag, + ) class Config(TiramisuAPI): @@ -1591,18 +1493,23 @@ class Config(TiramisuAPI): del self._config_bag.context del self._config_bag del self._orig_config_bags - except: + except ConfigError: pass 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, - children: 'Config'=[], + children: 'Config'=None, name=None, optiondescription: Optional[OptionDescription]=None, display_name=None ) -> None: + if children is None: + children = [] if isinstance(children, KernelMetaConfig): config = children else: @@ -1628,7 +1535,10 @@ class MetaConfig(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, optiondescription: OptionDescription, children: List[Config], @@ -1661,6 +1571,7 @@ class MixConfig(TiramisuAPI): class GroupConfig(TiramisuAPI): """GroupConfig that enables us to access the sub configuration's options""" + # pylint: disable=too-few-public-methods def __init__(self, children, name=None, diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 8d6af7d..3ac882b 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -24,6 +24,7 @@ from itertools import chain from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning from .i18n import _ from .setting import undefined, ConfigBag, OptionBag, Undefined +from .function import FUNCTION_WAITING_FOR_DICT # ____________________________________________________________ @@ -58,15 +59,15 @@ class Param: class ParamOption(Param): - __slots__ = ('todict', - 'option', + __slots__ = ('option', 'notraisepropertyerror', - 'raisepropertyerror') + 'raisepropertyerror', + ) def __init__(self, option: 'Option', notraisepropertyerror: bool=False, raisepropertyerror: bool=False, - todict: bool=False) -> None: + ) -> None: if __debug__ and not hasattr(option, 'impl_is_symlinkoption'): raise ValueError(_('paramoption needs an option not {}').format(type(option))) if option.impl_is_symlinkoption(): @@ -75,7 +76,6 @@ class ParamOption(Param): cur_opt = option 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)) - self.todict = todict self.option = cur_opt self.notraisepropertyerror = notraisepropertyerror self.raisepropertyerror = raisepropertyerror @@ -90,12 +90,10 @@ class ParamDynOption(ParamOption): notraisepropertyerror: bool=False, raisepropertyerror: bool=False, optional: bool=False, - todict: bool=False, ) -> None: super().__init__(option, notraisepropertyerror, raisepropertyerror, - todict, ) self.suffix = suffix self.dynoptiondescription = dynoptiondescription @@ -103,12 +101,11 @@ class ParamDynOption(ParamOption): class ParamSelfOption(Param): - __slots__ = ('todict', 'whole') + __slots__ = ('whole') def __init__(self, - todict: bool=False, - whole: bool=undefined) -> None: + whole: bool=undefined, + ) -> None: """whole: send all value for a multi, not only indexed value""" - self.todict = todict if whole is not undefined: self.whole = whole @@ -206,23 +203,13 @@ class Calculation: 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): pass -def manager_callback(callbk: Param, +def manager_callback(callback: Callable, + param: Param, option, index: Optional[int], orig_value, @@ -231,10 +218,10 @@ def manager_callback(callbk: Param, for_settings: bool, ) -> Any: """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 hasattr(callbk, 'whole'): - whole = callbk.whole + if hasattr(param, 'whole'): + whole = param.whole else: # if value is same_leadership, follower are isolate by default # otherwise option is a whole option @@ -243,7 +230,7 @@ def manager_callback(callbk: Param, return index return None - def calc_self(callbk, + def calc_self(param, option, index, value, @@ -251,10 +238,8 @@ def manager_callback(callbk: Param, ): # index must be apply only if 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 config_bag is undefined: - return undefined path = option.impl_getpath() option_bag = OptionBag(option, None, @@ -266,7 +251,7 @@ def manager_callback(callbk: Param, ) parent_option_bag, option_bag = get_option_bag(config_bag, option, - callbk, + param, apply_index, True, properties=properties, @@ -276,17 +261,17 @@ def manager_callback(callbk: Param, for idx in range(config_bag.context.get_length_leadership(parent_option_bag)): parent_option_bag, option_bag = get_option_bag(config_bag, option, - callbk, + param, idx, True, properties=properties, ) - new_value.append(get_value(callbk, + new_value.append(get_value(param, option_bag, path, )) else: - new_value = get_value(callbk, + new_value = get_value(param, option_bag, path, ) @@ -297,7 +282,7 @@ def manager_callback(callbk: Param, value = value[apply_index] return value - def get_value(callbk, + def get_value(param, option_bag, path, ): @@ -306,14 +291,14 @@ def manager_callback(callbk: Param, value = config_bag.context.get_value(option_bag) except PropertiesOptionError as err: # 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 ConfigError(_('unable to carry out a calculation for "{}"' ', {}').format(option.impl_get_display_name(), err), err) from 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 except AttributeError as err: - if isinstance(callbk, ParamDynOption) and callbk.optional: + if isinstance(param, ParamDynOption) and param.optional: # cannot acces, simulate a propertyerror raise PropertiesOptionError(option_bag, ['configerror'], @@ -324,7 +309,7 @@ def manager_callback(callbk: Param, def get_option_bag(config_bag, opt, - callbk, + param, index_, self_calc, properties=undefined, @@ -352,14 +337,14 @@ def manager_callback(callbk: Param, ) except PropertiesOptionError as err: # 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 ConfigError(_('unable to carry out a calculation for "{}"' ', {}').format(option.impl_get_display_name(), err), err) from 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 except AttributeError as err: - if isinstance(callbk, ParamDynOption) and callbk.optional: + if isinstance(param, ParamDynOption) and param.optional: # cannot acces, simulate a propertyerror raise PropertiesOptionError(options_bag[-1], ['configerror'], @@ -375,11 +360,11 @@ def manager_callback(callbk: Param, else: return options_bag[-1] - if isinstance(callbk, ParamValue): - return callbk.value + if isinstance(param, ParamValue): + return param.value - if isinstance(callbk, ParamInformation): - if isinstance(callbk, ParamSelfInformation): + if isinstance(param, ParamInformation): + if isinstance(param, ParamSelfInformation): option_bag = OptionBag(option, index, config_bag, @@ -387,48 +372,47 @@ def manager_callback(callbk: Param, else: option_bag = None try: - return config_bag.context.impl_get_information(config_bag, - option_bag, - callbk.information_name, - callbk.default_value, + return config_bag.context.impl_get_information(option_bag, + param.information_name, + param.default_value, ) except ValueError as err: raise ConfigError(_('option "{}" cannot be calculated: {}').format(option.impl_get_display_name(), str(err), )) - if isinstance(callbk, ParamIndex): + if isinstance(param, ParamIndex): return index - if isinstance(callbk, ParamSuffix): + if isinstance(param, ParamSuffix): if not option.issubdyn(): raise ConfigError(_('option "{}" is not in a dynoptiondescription').format(option.impl_get_display_name())) 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: raise Break() - value = calc_self(callbk, + value = calc_self(param, option, index, orig_value, config_bag, ) - if not callbk.todict: + if callback.__name__ not in FUNCTION_WAITING_FOR_DICT: return value return {'name': option.impl_get_display_name(), 'value': value, } - if isinstance(callbk, ParamOption): - callbk_option = callbk.option + if isinstance(param, ParamOption): + callbk_option = param.option callbk_options = None if callbk_option.issubdyn(): found = False - if isinstance(callbk, ParamDynOption): - subdyn = callbk.dynoptiondescription - rootpath = subdyn.impl_getpath() + callbk.suffix - suffix = callbk.suffix + if isinstance(param, ParamDynOption): + subdyn = param.dynoptiondescription + rootpath = subdyn.impl_getpath() + param.suffix + suffix = param.suffix callbk_option = callbk_option.to_dynoption(rootpath, suffix, subdyn, @@ -462,8 +446,6 @@ def manager_callback(callbk: Param, callbk_options.append(doption) if leadership_must_have_index and callbk_option.impl_is_follower() and index is None: raise Break() - if config_bag is undefined: - return undefined if callbk_options is None: callbk_options = [callbk_option] values = None @@ -471,7 +453,7 @@ def manager_callback(callbk: Param, values = [] for callbk_option in callbk_options: 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(): # leader index_ = None @@ -486,11 +468,11 @@ def manager_callback(callbk: Param, path = callbk_option.impl_getpath() option_bag = get_option_bag(config_bag, callbk_option, - callbk, + param, index_, False, ) - value = get_value(callbk, + value = get_value(param, option_bag, path, ) @@ -500,12 +482,10 @@ def manager_callback(callbk: Param, values.append(value) if values is not None: value = values - if not callbk.todict: + if callback.__name__ not in FUNCTION_WAITING_FOR_DICT: return value return {'name': callbk_option.impl_get_display_name(), 'value': value} - raise ConfigError(_('unknown callback type {} in option {}').format(callbk, - option.impl_get_display_name())) def carry_out_calculation(option, @@ -540,9 +520,10 @@ def carry_out_calculation(option, args = [] kwargs = {} 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: - value = manager_callback(callbk, + value = manager_callback(callback, + param, option, index, orig_value, @@ -550,16 +531,14 @@ def carry_out_calculation(option, leadership_must_have_index, for_settings, ) - if value is undefined: - return undefined if key is None: args.append(value) else: kwargs[key] = value except PropertiesOptionError as err: - if callbk.raisepropertyerror: + if param.raisepropertyerror: raise err - if callbk.todict: + if callback.__name__ in FUNCTION_WAITING_FOR_DICT: if key is None: args.append({'propertyerror': str(err)}) else: @@ -616,8 +595,6 @@ def calculate(option, raise err error = err except Exception as err: - # import traceback - # traceback.print_exc() error = err if args or kwargs: msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" ' diff --git a/tiramisu/cacheobj.py b/tiramisu/cacheobj.py index 2c8cfac..f23d988 100644 --- a/tiramisu/cacheobj.py +++ b/tiramisu/cacheobj.py @@ -16,79 +16,91 @@ # along with this program. If not, see . # ____________________________________________________________ from time import time -from .log import log - - -def _display_classname(obj): # pragma: no cover - return(obj.__class__.__name__.lower()) class Cache: + """cache object + """ __slots__ = ('_cache',) def __init__(self): 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 if follower, add index """ - if 'cache' in props or 'cache' in self_props: - self._cache.setdefault(path, {})[index] = (val, int(time()), validated) + if type_ == 'values': + 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): + """reset cache a a specified path + """ if path in self._cache: del self._cache[path] def get_cached(self): + """get cache values + """ return self._cache def reset_all_cache(self): + """reset all cache values + """ 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 diff --git a/tiramisu/config.py b/tiramisu/config.py index 374c5eb..165b5ad 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -18,16 +18,15 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -"options handler global entry point" +"""options handler global entry point +""" import weakref from copy import copy, deepcopy from typing import Optional, List, Any, Union - from .error import PropertiesOptionError, ConfigError, ConflictError, \ LeadershipError -from .option import SynDynOptionDescription, DynOptionDescription, Leadership, Option -from .option.baseoption import BaseOption +from .option import DynOptionDescription, Leadership, Option from .setting import OptionBag, ConfigBag, Settings, undefined, groups from .value import Values, owners from .i18n import _ @@ -48,7 +47,6 @@ class _SubConfig: def __init__(self, descr, context, - config_bag, subpath=None, ): """ Configuration option management class @@ -60,11 +58,6 @@ class _SubConfig: :type subpath: `str` with the path name """ # 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_context = context self._impl_path = subpath @@ -74,16 +67,6 @@ class _SubConfig: ): """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.remove_validation() option_bag = OptionBag(option_bag.option.get_leader(), @@ -92,22 +75,22 @@ class _SubConfig: ) 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): + """get root description + """ assert self._impl_descr is not None, _('there is no option description for this config' ' (may be GroupConfig)') return self._impl_descr 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): - return self.get_context()._impl_values + """get values object + """ + return self._impl_values # pylint: disable=no-member # ============================================================================= # CACHE @@ -129,18 +112,24 @@ class _SubConfig: ) option_bag.config_bag.properties = option_bag.config_bag.properties | {'cache'} else: - context = self.get_context() - context._impl_values_cache.reset_all_cache() - context.properties_cache.reset_all_cache() + self._impl_values_cache.reset_all_cache() # pylint: disable=no-member + self.properties_cache.reset_all_cache() # pylint: disable=no-member + + def get_values_cache(self): + """get cache for values + """ + return self._impl_values_cache # pylint: disable=no-member def reset_one_option_cache(self, resetted_opts, option_bag, ): + """reset cache for one option + """ if option_bag.path in resetted_opts: return 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() soption_bag = OptionBag(option, option_bag.index, @@ -178,7 +167,6 @@ class _SubConfig: subpath = '' 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, suffix, option_bag.option, @@ -192,7 +180,7 @@ class _SubConfig: None, 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(), None, False, @@ -258,15 +246,12 @@ class _SubConfig: :param first: return only one option if True, a list otherwise :return: find list or an exception if nothing has been found """ + # pylint: disable=too-many-arguments,too-many-locals def _filter_by_value(soption_bag): - try: - value = soption_bag.config_bag.context.get_value(soption_bag) - except PropertiesOptionError: - return False + value = self.get_value(soption_bag) if isinstance(value, list): return byvalue in value - else: - return value == byvalue + return value == byvalue found = False if only_path is not undefined: @@ -286,10 +271,10 @@ class _SubConfig: ) if byvalue is not undefined and not _filter_by_value(soption_bag): continue - elif option_bag.config_bag.properties: + if option_bag.config_bag.properties: #remove option with propertyerror, ... try: - self.get_sub_option_bag(option_bag, + self.get_sub_option_bag(option_bag, # pylint: disable=no-member path, None, True, @@ -331,16 +316,19 @@ class _SubConfig: return value def walk(self, - option_bag, - types=['option'], + option_bag: OptionBag, + types: List[str]=('option',), group_type=None, - recursive=True, - walked=False, + recursive: bool=True, + walked: bool=False, ): + """walk to tree + """ + # pylint: disable=too-many-branches,too-many-locals,too-many-arguments, if option_bag.option.impl_is_optiondescription(): # do not return root option if walked: - if 'optiondescription' in types and (group_type is None or + if 'optiondescription' in types and (group_type is None or option_bag.option.impl_get_group_type() == group_type): yield option_bag if not recursive: @@ -349,7 +337,7 @@ class _SubConfig: if not option_bag.option.impl_is_leadership(): for opt in option_bag.option.get_children(option_bag.config_bag): 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(), None, True, @@ -366,7 +354,7 @@ class _SubConfig: # it's a leadership so walk to leader and followers # followers has specific length 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(), None, True, @@ -375,7 +363,7 @@ class _SubConfig: values = self.get_value(leader_option_bag, need_help=False, ) - leadership_length = len(values) + ls_length = len(values) try: self._walk_valid_value(leader_option_bag, types, @@ -383,21 +371,19 @@ class _SubConfig: ) except PropertiesOptionError as err: if err.proptype in (['mandatory'], ['empty']): - if 'mandatory' in types: - yield leader_option_bag - else: - raise err - for idx in range(leadership_length): + yield leader_option_bag + for idx in range(ls_length): followers_dict[idx] = [] for follower in followers: follower_path = follower.impl_getpath() try: - for f_follower_bag in self.walk(self.get_sub_option_bag(option_bag, - follower_path, - idx, - True, - leadership_length=leadership_length, - )[-1], + options_bag = self.get_sub_option_bag(option_bag, # pylint: disable=no-member + follower_path, + idx, + True, + leadership_length=ls_length, + ) + for f_follower_bag in self.walk(options_bag[-1], types=types, recursive=recursive, group_type=group_type, @@ -410,7 +396,6 @@ class _SubConfig: continue if 'option' in types: yield followers_dict -# pass else: if 'mandatory' in types and not option_bag.option.impl_is_symlinkoption(): try: @@ -455,25 +440,13 @@ class _SubConfig: def set_value(self, option_bag: OptionBag, value: Any, - ): - if option_bag.option.impl_is_symlinkoption(): - raise ConfigError(_("can't set value to a SymLinkOption")) - context = option_bag.config_bag.context - context.get_settings().validate_properties(option_bag) - return context.get_values().set_value(option_bag, - 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) + ) -> Any: + """set value + """ + self.get_settings().validate_properties(option_bag) + return self.get_values().set_value(option_bag, + value + ) def get_value(self, option_bag, @@ -484,7 +457,9 @@ class _SubConfig: :return: option's value if name is an option name, OptionDescription 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): value = [] for opt_bag in option_bag: @@ -507,10 +482,10 @@ class _SubConfig: return value def _get(self, - option_bag, - parent_option_bag, - need_help, - ): + option_bag: OptionBag, + need_help: bool, + ) -> OptionBag: + # pylint: disable=too-many-locals option = option_bag.option if option.impl_is_symlinkoption(): suboption = option.impl_getopt() @@ -534,7 +509,7 @@ class _SubConfig: ret.append(doption_bag) return ret 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(), None, True, @@ -554,40 +529,28 @@ class _SubConfig: option_bag.config_bag, ori_option=option, ) - return self._get(soption_bag, None, need_help) + return self._get(soption_bag, + need_help, + ) return option_bag - def get_owner(self, - option_bag: OptionBag, - parent_option_bag=None, - ): - options_bag = self._get(option_bag, parent_option_bag, need_help=True) + def get_owner(self, option_bag: OptionBag): + """get owner + """ + options_bag = self._get(option_bag, + need_help=True, + ) if isinstance(options_bag, list): - if not option_bag.option.impl_is_symlinkoption(): - owner = [] - for opt_bag in options_bag: - owner.append(self.get_owner(opt_bag)) + for opt_bag in options_bag: + owner = self.get_owner(opt_bag) + if owner != owners.default: + break else: - for opt_bag in options_bag: - owner = self.get_owner(opt_bag) - if owner != owners.default: - break - else: - owner = owners.default + owner = owners.default else: owner = self.get_values().getowner(options_bag) 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): "abstract base class for the Config, KernelGroupConfig and the KernelMetaConfig" @@ -597,18 +560,21 @@ class _CommonConfig(_SubConfig): 'properties_cache', '_impl_permissives_cache', 'parents', - 'impl_type') + 'impl_type', + ) def _impl_build_all_caches(self, descr): if not descr.impl_already_build_caches(): - descr._group_type = groups.root - descr._build_cache(display_name=self._display_name) + descr._group_type = groups.root # pylint: disable=protected-access + descr._build_cache(display_name=self._display_name) # pylint: disable=no-member,protected-access if not hasattr(descr, '_cache_force_store_values'): raise ConfigError(_('option description seems to be part of an other ' 'config')) def get_parents(self): - for parent in self.parents: + """get parents + """ + for parent in self.parents: # pylint: disable=no-member yield parent() # information @@ -622,22 +588,19 @@ class _CommonConfig(_SubConfig): :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ - self._impl_values.set_information(config_bag, - None, - key, - value, - ) - context = config_bag.context - cache = context.get_description()._cache_dependencies_information.get(key, []) + self._impl_values.set_information(None, # pylint: disable=no-member + key, + value, + ) + cache = self.get_description()._cache_dependencies_information.get(key, []) # pylint: disable=protected-access for option in cache: option_bag = OptionBag(option, None, config_bag, ) - context.reset_cache(option_bag) + self.reset_cache(option_bag) def impl_get_information(self, - config_bag, option_bag, key, default, @@ -646,8 +609,7 @@ class _CommonConfig(_SubConfig): :param key: the item string (ex: "help") """ - return self._impl_values.get_information(config_bag, - option_bag, + return self._impl_values.get_information(option_bag, # pylint: disable=no-member key, default, ) @@ -656,24 +618,27 @@ class _CommonConfig(_SubConfig): key, raises=True, ): - self._impl_values.del_information(key, + """delete an information + """ + self._impl_values.del_information(key, # pylint: disable=no-member raises, ) 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): - raise NotImplementedError() - - def _gen_fake_values(self): - export = deepcopy(self.get_values()._values) + def gen_fake_values(self) -> 'KernelConfig': + """generate a fake values to improve validation when assign a new value + """ + export = deepcopy(self.get_values()._values) # pylint: disable=protected-access fake_config = KernelConfig(self._impl_descr, force_values=export, 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 def duplicate(self, @@ -684,10 +649,11 @@ class _CommonConfig(_SubConfig): deep=None, name=None, ): - if not isinstance(self, (KernelConfig, KernelMixConfig)): - raise ConfigError(_('cannot duplicate {}').format(self.__class__.__name__)) + """duplication config + """ + # pylint: disable=too-many-arguments if name is None: - name = self._impl_name + name = self._impl_name # pylint: disable=no-member if isinstance(self, KernelConfig): duplicated_config = KernelConfig(self._impl_descr, _duplicate=True, @@ -703,10 +669,10 @@ class _CommonConfig(_SubConfig): ) duplicated_values = duplicated_config.get_values() duplicated_settings = duplicated_config.get_settings() - duplicated_values._values = deepcopy(self.get_values()._values) - duplicated_values._informations = deepcopy(self.get_values()._informations) - duplicated_settings._properties = deepcopy(self.get_settings()._properties) - duplicated_settings._permissives = deepcopy(self.get_settings()._permissives) + duplicated_values._values = deepcopy(self.get_values()._values) # pylint: disable=protected-access + duplicated_values._informations = deepcopy(self.get_values()._informations) # pylint: disable=protected-access + duplicated_settings._properties = deepcopy(self.get_settings()._properties) # pylint: disable=protected-access + duplicated_settings._permissives = deepcopy(self.get_settings()._permissives) # pylint: disable=protected-access duplicated_settings.ro_append = self.get_settings().ro_append duplicated_settings.rw_append = self.get_settings().rw_append 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_config.reset_cache(None, 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)) - if self.parents: + if self.parents: # pylint: disable=no-member if deep is not None: - for parent in self.parents: + for parent in self.parents: # pylint: disable=no-member wparent = parent() if wparent not in deep: deep.append(wparent) @@ -731,17 +697,19 @@ class _CommonConfig(_SubConfig): name=subname, ) else: - duplicated_config.parents = self.parents - for parent in self.parents: - parent()._impl_children.append(duplicated_config) + duplicated_config.parents = self.parents # pylint: disable=no-member + for parent in self.parents: # pylint: disable=no-member + parent()._impl_children.append(duplicated_config) # pylint: disable=protected-access return duplicated_config def get_config_path(self): + """get config path + """ path = self.impl_getname() - for parent in self.parents: + for parent in self.parents: # pylint: disable=no-member wparent = parent() 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 return path @@ -755,6 +723,7 @@ class _CommonConfig(_SubConfig): ) -> List[OptionBag]: """Get the suboption for path and the name of the option :returns: tuple (config, name)""" + # pylint: disable=too-many-branches,too-many-locals,too-many-arguments if isinstance(bag, ConfigBag): option_bag = OptionBag(self.get_description(), None, @@ -763,8 +732,6 @@ class _CommonConfig(_SubConfig): else: option_bag = bag 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.split('.') last_idx = len(path) - 1 @@ -789,17 +756,18 @@ class _CommonConfig(_SubConfig): option_index = None if idx == last_idx: 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') if leadership_length is not None: length = leadership_length else: length = self.get_length_leadership(sub_option_bag) if index >= length: - raise LeadershipError(_('index "{}" is greater than the leadership length "{}" ' - 'for option "{}"').format(index, - length, - option.impl_get_display_name())) + raise LeadershipError(_(f'index "{index}" is greater than the leadership ' + f'length "{length}" for option ' + f'"{option.impl_get_display_name()}"')) option_properties = properties else: option_properties = undefined @@ -815,17 +783,22 @@ class _CommonConfig(_SubConfig): return options_bag def impl_getname(self): - return self._impl_name + """get config name + """ + return self._impl_name # pylint: disable=no-member # ____________________________________________________________ class KernelConfig(_CommonConfig): - "main configuration management entry" + """main configuration management entry + """ + # pylint: disable=too-many-instance-attributes __slots__ = ('__weakref__', '_impl_name', '_display_name', '_impl_symlink', - '_storage') + '_storage', + ) impl_type = 'config' def __init__(self, @@ -843,6 +816,7 @@ class KernelConfig(_CommonConfig): :param context: the current root config :type context: `Config` """ + # pylint: disable=too-many-arguments,too-many-arguments self._display_name = display_name self.parents = [] self._impl_symlink = [] @@ -870,11 +844,12 @@ class KernelConfig(_CommonConfig): super().__init__(descr, self._impl_context, None, - None, ) class KernelGroupConfig(_CommonConfig): + """Group a config with same optiondescription tree + """ __slots__ = ('__weakref__', '_impl_children', '_impl_name', @@ -886,18 +861,15 @@ class KernelGroupConfig(_CommonConfig): children, display_name=None, name=None, - _descr=None): - if not isinstance(children, list): - raise ConfigError(_("groupconfig's children must be a list")) + _descr=None, + ): + # pylint: disable=super-init-not-called names = [] for child in children: - if not isinstance(child, _CommonConfig): - raise ConfigError(_("groupconfig's children must be Config, MetaConfig or " - "GroupConfig")) name_ = child._impl_name names.append(name_) if len(names) != len(set(names)): - for idx in range(1, len(names) + 1): + while range(1, len(names) + 1): name = names.pop(0) if name in names: raise ConflictError(_('config name must be uniq in ' @@ -905,9 +877,6 @@ class KernelGroupConfig(_CommonConfig): self._impl_children = children self.parents = [] - config_bag = ConfigBag(self, - properties=None, - permissives=None) self._display_name = display_name if name: self._impl_name = name @@ -916,6 +885,8 @@ class KernelGroupConfig(_CommonConfig): self._impl_path = None def get_children(self): + """get all children + """ return self._impl_children def reset_cache(self, @@ -972,6 +943,7 @@ class KernelGroupConfig(_CommonConfig): value, ) except PropertiesOptionError as err: + # pylint: disable=protected-access ret.append(PropertiesOptionError(err._option_bag, err.proptype, err._settings, @@ -994,6 +966,7 @@ class KernelGroupConfig(_CommonConfig): ): """Find first not in current KernelGroupConfig, but in each children """ + # pylint: disable=too-many-arguments # if KernelMetaConfig, all children have same OptionDescription in # context so search only one time the option for all children if bypath is undefined and byname is not None and \ @@ -1002,14 +975,13 @@ class KernelGroupConfig(_CommonConfig): None, config_bag, ) - for bypath, byoption in self.find(root_option_bag, - bytype=None, - byname=byname, - byvalue=undefined, - raise_if_not_found=raise_if_not_found, - with_option=True, - ): - break + next(self.find(root_option_bag, + bytype=None, + byname=byname, + byvalue=undefined, + raise_if_not_found=raise_if_not_found, + with_option=True, + )) byname = None ret = [] @@ -1035,25 +1007,30 @@ class KernelGroupConfig(_CommonConfig): None, cconfig_bag, ) - for path in child.find(root_option_bag, - None, - byname, - byvalue, - raise_if_not_found=False, - only_path=bypath, - only_option=byoption, - ): + try: + next(child.find(root_option_bag, + None, + byname, + byvalue, + raise_if_not_found=False, + only_path=bypath, + only_option=byoption, + )) ret.append(child) - break + except StopIteration: + pass if not _sub: - self._find_return_results(ret != [], - raise_if_not_found) + self._find_return_results(ret != [], # pylint: disable=use-implicit-booleaness-not-comparison + raise_if_not_found, + ) return ret def reset(self, path: str, config_bag: ConfigBag, - ): + ) -> None: + """reset value for specified path + """ for child in self._impl_children: settings = child.get_settings() cconfig_bag = config_bag.copy() @@ -1072,7 +1049,10 @@ class KernelGroupConfig(_CommonConfig): child.get_values().reset(option_bag) def getconfig(self, - name): + name: str, + ) -> KernelConfig: + """get a child from a config name + """ for child in self._impl_children: if name == child.impl_getname(): return child @@ -1080,9 +1060,12 @@ class KernelGroupConfig(_CommonConfig): class KernelMixConfig(KernelGroupConfig): - __slots__ = ('_impl_name', - '_impl_symlink', - '_storage') + """Kernel mixconfig: this config can have differents optiondescription tree + """ + # pylint: disable=too-many-instance-attributes + __slots__ = ('_impl_symlink', + '_storage', + ) impl_type = 'mix' 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 this KernelMetaConfig """ + # pylint: disable=too-many-branches,too-many-nested-blocks,too-many-locals,too-many-arguments ret = [] if only_config: if force_default or force_default_if_same or force_dont_change_value: @@ -1147,10 +1131,11 @@ class KernelMixConfig(KernelGroupConfig): obj = self else: obj = child + validate_properties = not force_default and not force_default_if_same moption_bag = obj.get_sub_option_bag(cconfig_bag, option_bag.path, option_bag.index, - not force_default and not force_default_if_same, + validate_properties, )[-1] if force_default_if_same: if not child.get_values().hasvalue(option_bag.path): @@ -1167,6 +1152,7 @@ class KernelMixConfig(KernelGroupConfig): child_value, ) except PropertiesOptionError as err: + # pylint: disable=protected-access ret.append(PropertiesOptionError(err._option_bag, err.proptype, err._settings, @@ -1198,10 +1184,13 @@ class KernelMixConfig(KernelGroupConfig): return ret def reset(self, - path, - only_children, - config_bag, - ): + path: str, + only_children: bool, + config_bag: ConfigBag, + ) -> None: + """reset value for a specified path + """ + # pylint: disable=arguments-differ rconfig_bag = config_bag.copy() rconfig_bag.remove_validation() if self.impl_type == 'meta': @@ -1338,8 +1327,8 @@ class KernelMetaConfig(KernelMixConfig): new_children = [] for child_name in children: assert isinstance(child_name, str), _('MetaConfig with optiondescription' - ' must have string has child, ' - 'not {}').format(child_name) + ' must have string has child, ' + 'not {}').format(child_name) new_children.append(KernelConfig(optiondescription, name=child_name)) children = new_children descr = optiondescription diff --git a/tiramisu/function.py b/tiramisu/function.py index 0b8b2f2..4ae7ef3 100644 --- a/tiramisu/function.py +++ b/tiramisu/function.py @@ -12,6 +12,8 @@ # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . +"""some functions to validates or calculates value +""" from typing import Any, List, Optional from operator import add, mul, sub, truediv from ipaddress import ip_address, ip_interface, ip_network @@ -20,145 +22,130 @@ from .setting import undefined from .error import display_list -def valid_network_netmask(network: str, - netmask: str): - """FIXME +FUNCTION_WAITING_FOR_DICT = [] + + +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): - network_value = network['value'] - network_display_name = '({})'.format(network['name']) - else: - network_value = network - network_display_name = '' - if None in [network_value, netmask]: + name = function.__name__ + if name not in FUNCTION_WAITING_FOR_DICT: + FUNCTION_WAITING_FOR_DICT.append(name) + return function + + +@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 try: - ip_network('{0}/{1}'.format(network_value, netmask)) - except ValueError: - raise ValueError(_('network "{0}" {1}does not match with this netmask').format(network_value, - network_display_name)) + ip_network(f'{network["value"]}/{netmask["value"]}') + except ValueError as err: + raise ValueError(_(f'network "{network["value"]}" ({network["name"]}) does not match ' + 'with this netmask')) from err -def valid_ip_netmask(ip: str, - netmask: str): - if isinstance(ip, dict): - ip_value = ip['value'] - ip_display_name = '({})'.format(ip['name']) - else: - ip_value = ip - ip_display_name = '' - if None in [ip_value, netmask]: + +@function_waiting_for_dict +def valid_ip_netmask(ip: dict, # pylint: disable=invalid-name + netmask: dict, + ): + """validates if ip and netmask are coherent + this validator must be set to netmask option + """ + if None in [ip['value'], netmask['value']]: 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: - raise ValueError(_('IP \"{0}\" {1}with this netmask is in fact a network address').format(ip_value, ip_display_name)) - elif ip_netmask.ip == ip_netmask.network.broadcast_address: - raise ValueError(_('IP \"{0}\" {1}with this netmask is in fact a broacast address').format(ip_value, ip_display_name)) + msg = _(f'IP "{ip["value"]}" ({ip["name"]}) with this netmask is ' + 'in fact a network address') + 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 ? -def valid_broadcast(network: 'NetworkOption', - netmask: 'NetmaskOption', - broadcast: 'BroadcastOption'): - 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 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: +@function_waiting_for_dict +def valid_broadcast(network: dict, + netmask: dict, + broadcast: dict, + ): + """validates if the broadcast is coherent with network and netmask + """ + if None in [network['value'], netmask['value'], broadcast['value']]: return - if '/' in network_value: - network_obj = ip_network('{0}'.format(network_value)) + if ip_network(f'{network["value"]}/{netmask["value"]}').broadcast_address != \ + 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: - if netmask_value is None: + if netmask is None or netmask['value'] is None: return - network_obj = ip_network('{0}/{1}'.format(network_value, - netmask_value)) - if ip_interface(ip) not in network_obj: + network_value = f'{network["value"]}/{netmask["value"]}' + network_obj = ip_network(network_value) + ip_netmask = ip_interface(f'{ip["value"]}/{network_obj.netmask}') + if ip_netmask not in network_obj: if netmask is None: - msg = _('this IP is not in network {0}{1}').format(network_value, - network_display_name) + msg = _('this IP is not in network {network["value"]} ({network["name"]})') else: - msg = _('this IP is not in network {0}{1} with netmask {2}{3}').format(network_value, - network_display_name, - netmask_value, - netmask_display_name) + msg = _('this IP is not in network {network["value"]} ({network["name"]}) ' + 'with netmask {netmask["value"]} ({netmask["name"]})') raise ValueError(msg) - # 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 netmask is None: - msg = _('this IP with the network {0}{1} is in fact a network address').format(network_value, - network_display_name) - else: - msg = _('this IP with the netmask {0}{1} is in fact a network address').format(netmask_value, - netmask_display_name) + msg = _(f'this IP with the network {network["value"]} ({network["value"]} ' + 'is in fact a network address') raise ValueError(msg) - elif ip_netmask.ip == ip_netmask.network.broadcast_address: - if netmask is None: - msg = _('this IP with the network {0}{1} is in fact a broadcast address').format(network_value, - network_display_name) - else: - msg = _('this IP with the netmask {0}{1} is in fact a broadcast address').format(netmask_value, - netmask_display_name) + if ip_netmask.ip == ip_netmask.network.broadcast_address: + msg = _(f'this IP with the network {network["value"]} ({network["value"]} ' + 'is in fact a broadcast address') raise ValueError(msg) +@function_waiting_for_dict def valid_not_equal(*values): + """valid that two options have not same value + """ equal = set() - for idx, val in enumerate(values[1:]): - if isinstance(val, dict): - if 'propertyerror' in val: - continue - tval = val['value'] - else: - tval = val - if values[0] == tval is not None: - if isinstance(val, dict): - 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) + for val in values[1:]: + if 'propertyerror' in val: + continue + if values[0]['value'] == val['value'] is not None: + equal.add(val['name']) + if not equal: + return + msg = _(f'value is identical to {display_list(list(equal), add_quote=True)}') + raise ValueError(msg) class CalcValue: + """class to calc_value with different functions + """ + # pylint: disable=too-many-instance-attributes def __call__(self, *args: List[Any], multi: bool=False, @@ -175,6 +162,7 @@ class CalcValue: operator: Optional[str]=None, index: Optional[int]=None, **kwargs) -> Any: + # pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals """calculate value :param args: 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 if there is more than one condition, set condition_0, condition_1, ... :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 - 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 allow_none: if False, do not return list in None is present in list :param remove_duplicate_value: if True, remote duplicated value @@ -196,28 +186,36 @@ class CalcValue: examples: * 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') - >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 'val1', 'val2': 'val1'} * 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') >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']} * 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') - >>> 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]) >>> cfg = Config(od) >>> cfg.property.read_write() @@ -228,13 +226,15 @@ class CalcValue: {'val2': '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) >>> val1 = StrOption('val1', '', 'val1') - >>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), - ... default=ParamValue('default_value'), - ... condition=ParamOption(boolean), - ... expected=ParamValue(True))) + >>> val2 = StrOption('val2', '', callback=calc_value, + ... callback_params=Params(ParamOption(val1, True), + ... default=ParamValue('default_value'), + ... condition=ParamOption(boolean), + ... expected=ParamValue(True))) >>> od = OptionDescription('root', '', [boolean, val1, val2]) >>> cfg = Config(od) >>> cfg.property.read_write() @@ -245,41 +245,55 @@ class CalcValue: {'boolean': False, 'val1': 'val1', 'val2': 'default_value'} * 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') >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 'val1', 'val2': None, 'val3': ['val1', None]} * 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') >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']} * 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') >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'} * 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') >>> val2 = StrOption('val2', "", 'val2') >>> 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]) >>> cfg = Config(od) >>> cfg.property.read_write() @@ -290,25 +304,31 @@ class CalcValue: {'val1': 'val1', 'val2': 'val2', 'val4': ''} * 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) >>> 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]) >>> cfg = Config(od) >>> cfg.value.dict() {'val1': 1, 'val2': 2, 'val3': 3} """ + # pylint: disable=attribute-defined-outside-init self.args = args self.condition = condition self.expected = expected self.condition_operator = condition_operator self.reverse_condition = reverse_condition 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, - min_args_len) + min_args_len, + ) if not multi: if join is not None: if None not in value: @@ -317,12 +337,13 @@ class CalcValue: value = None elif value and operator: new_value = value[0] - op = {'mul': mul, - 'add': add, - 'div': truediv, - 'sub': sub}[operator] + oper = {'mul': mul, + 'add': add, + 'div': truediv, + 'sub': sub, + }[operator] for val in value[1:]: - new_value = op(new_value, val) + new_value = oper(new_value, val) value = new_value elif value == []: value = None @@ -344,7 +365,9 @@ class CalcValue: break lval = len(val) 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 new_value = [] if length_val is not None: @@ -374,6 +397,9 @@ class CalcValue: pattern: str, to_dict: bool=False, empty_test=undefined) -> Any: + """get value from kwargs + """ + # pylint: disable=too-many-branches # if value attribute exist return it's value # otherwise pattern_0, pattern_1, ... # otherwise undefined @@ -385,10 +411,9 @@ class CalcValue: else: kwargs_matches = {} len_pattern = len(pattern) - for key in self.kwargs.keys(): + for key, pattern_value in self.kwargs.items(): if key.startswith(pattern): index = int(key[len_pattern:]) - pattern_value = self.kwargs[key] if isinstance(pattern_value, dict): pattern_value = pattern_value['value'] kwargs_matches[index] = pattern_value @@ -408,21 +433,28 @@ class CalcValue: return returns 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, 'condition_', - to_dict='all') + to_dict='all', + ) if calculated_conditions is undefined: is_matches = not self.no_condition_is_invalid else: is_matches = None calculated_expected = self.value_from_kwargs(self.expected, 'expected_', - to_dict=True) + to_dict=True, + ) calculated_reverse = self.value_from_kwargs(self.reverse_condition, 'reverse_condition_', to_dict=True, - empty_test=False) + empty_test=False, + ) for idx, calculated_condition in calculated_conditions.items(): if isinstance(calculated_expected, dict): if idx is not None: @@ -453,14 +485,20 @@ class CalcValue: if is_matches: break 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 \ or not is_matches and self.reverse_condition return is_matches def get_value(self, default, - min_args_len): + min_args_len, + ): + """get the value from arguments + """ + # retrieve the condition if isinstance(self.condition, dict): if 'value' in self.condition: condition_value = self.condition['value'] @@ -468,18 +506,19 @@ class CalcValue: condition_value = undefined else: condition_value = self.condition - condition_matches = self.is_condition_matches(condition_value) - if not condition_matches: - # force to default + # value is empty if condition doesn't match + # otherwise value is arg + if not self.is_condition_matches(condition_value): value = [] else: value = self.get_args() if min_args_len and not len(value) >= min_args_len: value = [] - if value == []: + if not value: # default value new_default = self.value_from_kwargs(default, - 'default_') + 'default_', + ) if new_default is not undefined: if not isinstance(new_default, list): value = [new_default] @@ -488,29 +527,34 @@ class CalcValue: return value def get_args(self): + """get all arguments + """ return list(self.args) class CalcValuePropertyHelp(CalcValue): + """special class to display property error + """ def get_name(self): + """get the condition name + """ return self.condition['name'] - def get_indexed_name(self, index): - return self.kwargs.get(f'condition_{index}')['name'] + def get_indexed_name(self, index: int) -> str: + """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): - #if isinstance(option, tuple): - # if not reverse: - # msg = _('the calculated value is {0}').format(display_value) - # else: - # msg = _('the calculated value is not {0}').format(display_value) - #else: + def build_property_message(self, + name: str, + value: Any, + ) -> str: + """prepare message to display error message if needed + """ if not self.reverse_condition: msg = _('the value of "{0}" is {1}').format(name, value) else: @@ -519,40 +563,35 @@ class CalcValuePropertyHelp(CalcValue): def get_args(self): args = super().get_args() - if args: - if len(self.args) != 1: - raise ValueError(_('only one property is allowed for a calculation')) - action = args[0] - calculated_expected = self.value_from_kwargs(self.expected, - 'expected_', - to_dict=True) - 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()) + action = args[0] + calculated_expected = self.value_from_kwargs(self.expected, + 'expected_', + to_dict=True) + if self.condition is not undefined: + if 'propertyerror' in self.condition: + msg = self.condition['propertyerror'] else: - return [(action, f'"{action}"')] - return [(action, f'"{action}" ({msg})')] - return - ## calc_properties.setdefault(action, []).append(msg) + 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_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.__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.__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) diff --git a/tiramisu/log.py b/tiramisu/log.py index 178f5b6..22c3005 100644 --- a/tiramisu/log.py +++ b/tiramisu/log.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from logging import getLogger, DEBUG, basicConfig, StreamHandler, Formatter +from logging import getLogger, DEBUG, StreamHandler, Formatter import os diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py index b55ce22..9a2938c 100644 --- a/tiramisu/option/__init__.py +++ b/tiramisu/option/__init__.py @@ -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 . +# +# 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 .dynoptiondescription import DynOptionDescription from .syndynoptiondescription import SynDynOptionDescription, SynDynLeadership diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 31f686a..d7803cc 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -18,18 +18,16 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from types import FunctionType -from typing import FrozenSet, Callable, Tuple, Set, Optional, Union, Any, List +"""base option +""" +from typing import FrozenSet, Set, Any, List import weakref -from inspect import signature from itertools import chain from ..i18n import _ -from ..setting import undefined, Settings -from ..value import Values -from ..error import ConfigError, display_list -from ..autolib import Calculation, Params, ParamOption +from ..setting import undefined +from ..autolib import Calculation, ParamOption STATIC_TUPLE = frozenset() @@ -38,6 +36,8 @@ submulti = 2 def valid_name(name): + """valid option name + """ if not isinstance(name, str): return False # if '.' in name: @@ -73,7 +73,8 @@ class Base: elif isinstance(properties, tuple): properties = frozenset(properties) 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 if 'notunique' not in properties: properties = properties | {'unique'} @@ -85,7 +86,8 @@ class Base: for prop in properties: if not isinstance(prop, str): 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()): if isinstance(param, ParamOption): param.option._add_dependency(self) @@ -96,7 +98,10 @@ class Base: _setattr(self, '_properties', properties) 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: return getattr(self, '_has_dependency', False) return hasattr(self, '_dependencies') @@ -107,7 +112,7 @@ class Base: ret = set(getattr(self, '_dependencies', STATIC_TUPLE)) if context_od and hasattr(context_od, '_dependencies'): # add options that have context is set in calculation - return set(context_od._dependencies) | ret + return set(context_od._dependencies) | ret # pylint: disable=protected-access return ret def _get_suffixes_dependencies(self) -> Set[str]: @@ -119,26 +124,32 @@ class Base: ) -> None: woption = weakref.ref(option) options = self._get_dependencies(None) - options.add(weakref.ref(option)) - self._dependencies = tuple(options) + options.add(woption) + self._dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init if is_suffix: options = list(self._get_suffixes_dependencies()) - options.append(weakref.ref(option)) - self._suffixes_dependencies = tuple(options) + options.append(woption) + self._suffixes_dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init def impl_is_optiondescription(self) -> bool: + """option is an option description + """ return False def impl_is_dynoptiondescription(self) -> bool: + """option is not a dyn option description + """ return False def impl_getname(self) -> str: - return self._name + """get name + """ + return self._name # pylint: disable=no-member def _set_readonly(self) -> None: - if isinstance(self._informations, dict): + if isinstance(self._informations, dict): # pylint: disable=no-member _setattr = object.__setattr__ - dico = self._informations + dico = self._informations # pylint: disable=no-member keys = tuple(dico.keys()) if len(keys) == 1: dico = dico['doc'] @@ -150,21 +161,30 @@ class Base: _setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())])) def impl_is_readonly(self) -> str: + """the option is readonly + """ # _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]: + """get properties + """ return getattr(self, '_properties', frozenset()) def _setsubdyn(self, subdyn, ) -> None: + # pylint: disable=attribute-defined-outside-init self._subdyn = subdyn def issubdyn(self) -> bool: + """is sub dynoption + """ return getattr(self, '_subdyn', None) is not None def getsubdyn(self): + """get sub dynoption + """ return self._subdyn() # ____________________________________________________________ @@ -177,7 +197,7 @@ class Base: :param key: the item string (ex: "help") """ - dico = self._informations + dico = self._informations # pylint: disable=no-member if isinstance(dico, tuple): if key in dico[0]: return dico[1][dico[0].index(key)] @@ -189,7 +209,9 @@ class Base: return dico[key] if default is not undefined: 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, key: str, @@ -206,13 +228,15 @@ class Base: " read-only").format(self.__class__.__name__, self, key)) - self._informations[key] = value + self._informations[key] = value # pylint: disable=no-member 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): return list(dico[0]) - elif isinstance(dico, str): + if isinstance(dico, str): return ['doc'] # it's a dict return list(dico.keys()) @@ -225,9 +249,6 @@ class BaseOption(Base): """ __slots__ = ('_display_name_function',) - def __getstate__(self): - raise NotImplementedError() - def __setattr__(self, name: str, value: Any) -> Any: @@ -245,19 +266,16 @@ class BaseOption(Base): ' read-only').format(self.__class__.__name__, self.impl_get_display_name(), name)) - super(BaseOption, self).__setattr__(name, value) + super().__setattr__(name, value) def impl_getpath(self) -> str: + """get the path of the option + """ try: return self._path - except AttributeError: - raise AttributeError(_('"{}" not part of any Config').format(self.impl_get_display_name())) - - 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 + except AttributeError as err: + raise AttributeError(_(f'"{self.impl_get_display_name()}" not part of any Config')) \ + from err def _impl_get_display_name(self, dyn_name: Base=None, @@ -287,6 +305,8 @@ class BaseOption(Base): ) def impl_get_display_name(self) -> str: + """get display name + """ return self._get_display_name(None, None, ) @@ -294,19 +314,26 @@ class BaseOption(Base): def reset_cache(self, path: str, config_bag: 'OptionBag', - resetted_opts: List[Base]) -> None: + resetted_opts: List[Base], # pylint: disable=unused-argument + ) -> None: + """reset cache + """ context = config_bag.context 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(): - context._impl_values_cache.delcache(path) + context.get_values_cache().delcache(path) # pylint: disable=protected-access def impl_is_symlinkoption(self) -> bool: + """the option is not a symlinkoption + """ return False def get_dependencies_information(self, itself=False, ) -> List[str]: + """get dependencies information + """ if itself: idx = 1 else: diff --git a/tiramisu/option/booloption.py b/tiramisu/option/booloption.py index 7beffef..ebbba2d 100644 --- a/tiramisu/option/booloption.py +++ b/tiramisu/option/booloption.py @@ -18,19 +18,23 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""BoolOption +""" -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ from .option import Option class BoolOption(Option): - "represents a choice between ``True`` and ``False``" + """represents a choice between ``True`` and ``False`` + """ __slots__ = tuple() - _type = 'boolean' - _display_name = _('boolean') + _type = _('boolean') def validate(self, - value: bool) -> None: + value: bool, + ) -> None: + """validate value + """ if not isinstance(value, bool): raise ValueError() diff --git a/tiramisu/option/broadcastoption.py b/tiramisu/option/broadcastoption.py index 941769d..52423d4 100644 --- a/tiramisu/option/broadcastoption.py +++ b/tiramisu/option/broadcastoption.py @@ -18,21 +18,25 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # 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 .option import Option class BroadcastOption(Option): + """represents the choice of a broadcast + """ __slots__ = tuple() - _type = 'broadcast_address' - _display_name = _('broadcast address') + _type = _('broadcast address') def validate(self, - value: str) -> None: + value: str, + ) -> None: + """validate + """ if not isinstance(value, str): raise ValueError(_('invalid string')) if value.count('.') != 3: @@ -42,5 +46,5 @@ class BroadcastOption(Option): raise ValueError() try: ip_address(value) - except ValueError: - raise ValueError() + except ValueError as err: + raise ValueError() from err diff --git a/tiramisu/option/choiceoption.py b/tiramisu/option/choiceoption.py index 61fdb52..49a5b5c 100644 --- a/tiramisu/option/choiceoption.py +++ b/tiramisu/option/choiceoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""ChoiceOption +""" from typing import Any from ..setting import undefined, OptionBag @@ -33,8 +35,7 @@ class ChoiceOption(Option): The option can also have the value ``None`` """ __slots__ = tuple() - _type = 'choice' - _display_name = _('choice') + _type = _('choice') def __init__(self, name, @@ -56,7 +57,10 @@ class ChoiceOption(Option): **kwargs) def impl_get_values(self, - option_bag): + option_bag: OptionBag, + ): + """get values allowed by option + """ if isinstance(self._choice_values, Calculation): values = self._choice_values.execute(option_bag) if values is not undefined and not isinstance(values, list): @@ -67,20 +71,18 @@ class ChoiceOption(Option): return values def validate(self, - value: Any) -> None: - pass - - 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) + value: Any, + ) -> None: + """nothing to valide + """ def validate_with_option(self, - value: Any, - option_bag: OptionBag) -> None: + value: Any, + option_bag: OptionBag, + loaded: bool, + ) -> None: + if loaded and isinstance(self._choice_values, Calculation): + return values = self.impl_get_values(option_bag) self.validate_values(value, values) @@ -88,6 +90,8 @@ class ChoiceOption(Option): value, values, ) -> None: + """validate values + """ if values is not undefined and value not in values: if len(values) == 1: raise ValueError(_('only "{0}" is allowed' diff --git a/tiramisu/option/dateoption.py b/tiramisu/option/dateoption.py index 341b4a2..baf836d 100644 --- a/tiramisu/option/dateoption.py +++ b/tiramisu/option/dateoption.py @@ -18,22 +18,24 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""DateOption +""" from datetime import datetime -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ from .stroption import StrOption class DateOption(StrOption): + """represents the choice of a date + """ __slots__ = tuple() - _type = 'date' - _display_name = _('date') + _type = _('date') def validate(self, value: str) -> None: super().validate(value) try: datetime.strptime(value, "%Y-%m-%d") - except ValueError: - raise ValueError() + except ValueError as err: + raise ValueError() from err diff --git a/tiramisu/option/domainnameoption.py b/tiramisu/option/domainnameoption.py index fb21ae5..df888b5 100644 --- a/tiramisu/option/domainnameoption.py +++ b/tiramisu/option/domainnameoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""DomainnameOption +""" import re from ipaddress import ip_interface from typing import Any, Optional, List @@ -38,8 +40,7 @@ class DomainnameOption(StrOption): fqdn: with tld, not supported yet """ __slots__ = tuple() - _type = 'domainname' - _display_name = _('domain name') + _type = _('domain name') def __init__(self, name: str, @@ -54,8 +55,9 @@ class DomainnameOption(StrOption): allow_cidr_network: bool=False, type: str='domainname', 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']: raise ValueError(_('unknown type {0} for hostname').format(type)) extra = {'_dom_type': type} @@ -111,11 +113,10 @@ class DomainnameOption(StrOption): warnings_only=warnings_only, extra=extra) - def _get_len(self, type): - if type == 'netbios': + def _get_len(self, type_): + if type_ == 'netbios': return 15 - else: - return 63 + return 63 def _validate_domain(self, value: str) -> None: diff --git a/tiramisu/option/dynoptiondescription.py b/tiramisu/option/dynoptiondescription.py index e2098d8..e71e5e0 100644 --- a/tiramisu/option/dynoptiondescription.py +++ b/tiramisu/option/dynoptiondescription.py @@ -18,9 +18,11 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""DynOptionDescription +""" import re import weakref -from typing import List, Callable, Any +from typing import List, Any from itertools import chain from ..autolib import ParamOption @@ -28,7 +30,7 @@ from ..autolib import ParamOption from ..i18n import _ from .optiondescription import OptionDescription from .baseoption import BaseOption -from ..setting import OptionBag, ConfigBag, groups, undefined +from ..setting import OptionBag, ConfigBag from ..error import ConfigError from ..autolib import Calculation @@ -37,6 +39,8 @@ NAME_REGEXP = re.compile(r'^[a-zA-Z\d\-_]*$') class DynOptionDescription(OptionDescription): + """dyn option description + """ __slots__ = ('_suffixes',) def __init__(self, @@ -46,7 +50,7 @@ class DynOptionDescription(OptionDescription): suffixes: Calculation, properties=None, ) -> None: - + # pylint: disable=too-many-arguments super().__init__(name, doc, children, @@ -69,6 +73,8 @@ class DynOptionDescription(OptionDescription): def convert_suffix_to_path(self, suffix: Any, ) -> str: + """convert suffix to use it to a path + """ if suffix is None: return None if not isinstance(suffix, str): @@ -78,7 +84,10 @@ class DynOptionDescription(OptionDescription): return suffix def get_suffixes(self, - config_bag: ConfigBag) -> List[str]: + config_bag: ConfigBag, + ) -> List[str]: + """get dynamic suffixes + """ option_bag = OptionBag(self, None, config_bag, @@ -90,8 +99,9 @@ class DynOptionDescription(OptionDescription): values_ = [] if __debug__: if not isinstance(values, list): - raise ValueError(_('DynOptionDescription suffixes for option "{}", is not a list ({})' - '').format(self.impl_get_display_name(), values)) + raise ValueError(_('DynOptionDescription suffixes for ' + f'option "{self.impl_get_display_name()}", is not ' + f'a list ({values})')) for val in values: cval = self.convert_suffix_to_path(val) if not isinstance(cval, str) or re.match(NAME_REGEXP, cval) is None: @@ -106,8 +116,8 @@ class DynOptionDescription(OptionDescription): extra_values = values_.copy() for val in set(values_): extra_values.remove(val) - raise ValueError(_('DynOptionDescription suffixes return a list with multiple value ' - '"{}"''').format(extra_values)) + raise ValueError(_('DynOptionDescription suffixes return a list with ' + f'multiple value "{extra_values}"''')) return values_ def impl_is_dynoptiondescription(self) -> bool: diff --git a/tiramisu/option/emailoption.py b/tiramisu/option/emailoption.py index a0ec7df..95684eb 100644 --- a/tiramisu/option/emailoption.py +++ b/tiramisu/option/emailoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""EmailOption +""" import re from ..i18n import _ @@ -25,7 +27,8 @@ from .stroption import RegexpOption class EmailOption(RegexpOption): + """represents a choice of an email + """ __slots__ = tuple() _regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$") - _type = 'email' - _display_name = _('email address') + _type = _('email address') diff --git a/tiramisu/option/filenameoption.py b/tiramisu/option/filenameoption.py index c13b4de..c5eb090 100644 --- a/tiramisu/option/filenameoption.py +++ b/tiramisu/option/filenameoption.py @@ -18,16 +18,17 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -import re - +"""FilenameOption +""" from ..i18n import _ from .stroption import StrOption class FilenameOption(StrOption): + """represents a choice of a file name + """ __slots__ = tuple() - _type = 'filename' - _display_name = _('file name') + _type = _('file name') def validate(self, value: str, diff --git a/tiramisu/option/floatoption.py b/tiramisu/option/floatoption.py index 9944e50..285fa81 100644 --- a/tiramisu/option/floatoption.py +++ b/tiramisu/option/floatoption.py @@ -18,17 +18,18 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""FloatOption +""" -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ from .option import Option class FloatOption(Option): - "represents a choice of a floating point number" + """represents a choice of a floating point number + """ __slots__ = tuple() - _type = 'float' - _display_name = _('float') + _type = _('float') def validate(self, value: float) -> None: diff --git a/tiramisu/option/intoption.py b/tiramisu/option/intoption.py index 154e61c..a7a6e4e 100644 --- a/tiramisu/option/intoption.py +++ b/tiramisu/option/intoption.py @@ -18,8 +18,9 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""IntOption +""" -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ from .option import Option @@ -27,8 +28,7 @@ from .option import Option class IntOption(Option): "represents a choice of an integer" __slots__ = tuple() - _type = 'integer' - _display_name = _('integer') + _type = _('integer') def __init__(self, *args, @@ -43,7 +43,8 @@ class IntOption(Option): super().__init__(*args, extra=extra, **kwargs) def validate(self, - value: int) -> None: + value: int, + ) -> None: if not isinstance(value, int): raise ValueError() diff --git a/tiramisu/option/ipoption.py b/tiramisu/option/ipoption.py index f3e8d71..c548159 100644 --- a/tiramisu/option/ipoption.py +++ b/tiramisu/option/ipoption.py @@ -18,21 +18,19 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""IPOption +""" from ipaddress import ip_address, ip_interface -from ..error import ConfigError -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ -from .option import Option from .stroption import StrOption -from ..function import valid_ip_netmask class IPOption(StrOption): - "represents the choice of an ip" + """represents the choice of an ip + """ __slots__ = tuple() - _type = 'ip' - _display_name = _('IP') + _type = _('IP') def __init__(self, *args, @@ -52,19 +50,19 @@ class IPOption(StrOption): def _validate_cidr(self, value): try: - ip = ip_interface(value) - except ValueError: - raise ValueError() - if ip.ip == ip.network.network_address: + ip_obj = ip_interface(value) + except ValueError as err: + raise ValueError() from err + if ip_obj.ip == ip_obj.network.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")) def _validate_ip(self, value): try: - new_value = str(ip_address(value)) - except ValueError: - raise ValueError() + str(ip_address(value)) + except ValueError as err: + raise ValueError() from err def validate(self, value: str) -> None: @@ -79,14 +77,14 @@ class IPOption(StrOption): def second_level_validation(self, value: str, warnings_only: bool) -> None: - ip = ip_interface(value) - if not self.impl_get_extra('_allow_reserved') and ip.is_reserved: + ip_obj = ip_interface(value) + if not self.impl_get_extra('_allow_reserved') and ip_obj.is_reserved: if warnings_only: msg = _("shouldn't be reserved IP") else: msg = _("mustn't be reserved IP") raise ValueError(msg) - if self.impl_get_extra('_private_only') and not ip.is_private: + if self.impl_get_extra('_private_only') and not ip_obj.is_private: if warnings_only: msg = _("should be private IP") else: diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index 0364985..fb1157b 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -20,22 +20,23 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import weakref -from itertools import chain -from typing import List, Iterator, Optional, Any +from typing import List, Iterator, Optional from ..i18n import _ -from ..setting import groups, undefined, OptionBag, Settings, ALLOWED_LEADER_PROPERTIES -from ..value import Values +from ..setting import groups, undefined, OptionBag, ALLOWED_LEADER_PROPERTIES from .optiondescription import OptionDescription from .syndynoptiondescription import SynDynLeadership from .baseoption import BaseOption from .option import Option from ..error import LeadershipError -from ..autolib import Calculation, ParamOption +from ..autolib import Calculation class Leadership(OptionDescription): + """Leadership + """ + # pylint: disable=too-many-arguments __slots__ = ('leader', 'followers', ) @@ -57,35 +58,10 @@ class Leadership(OptionDescription): leader = children[0] for idx, child in enumerate(children): if __debug__: - 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())) - 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())) + self._check_child_is_valid(child) if idx != 0: + if __debug__: + self._check_default_value(child) # remove empty property for follower child._properties = frozenset(child._properties - {'empty', 'unique'}) followers.append(child) @@ -96,35 +72,78 @@ class Leadership(OptionDescription): if prop not in ALLOWED_LEADER_PROPERTIES and not isinstance(prop, Calculation): 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, subdyn, ) -> None: + # pylint: disable=attribute-defined-outside-init,protected-access for chld in self._children[1]: chld._setsubdyn(subdyn) self._subdyn = subdyn def is_leader(self, - opt: Option) -> bool: + opt: Option, + ) -> bool: + """the option is the leader + """ leader = self.get_leader() return opt == leader or (opt.impl_is_dynsymlinkoption() and opt.opt == leader) def get_leader(self) -> Option: + """get leader + """ return self._children[1][0] def get_followers(self) -> Iterator[Option]: + """get all followers + """ for follower in self._children[1][1:]: yield follower - def in_same_group(self, - opt: Option) -> bool: + def in_same_leadership(self, + opt: Option, + ) -> bool: + """check if followers are in same leadership + """ if opt.impl_is_dynsymlinkoption(): opt = opt.opt return opt in self._children[1] - def reset(self, - values: Values, - option_bag: OptionBag) -> None: - config_bag = option_bag.config_bag.copy() + def reset(self, config_bag: 'ConfigBag') -> None: + """reset follower value + """ + values = config_bag.context.get_values() + config_bag = config_bag.copy() config_bag.remove_validation() for follower in self.get_followers(): soption_bag = OptionBag(follower, @@ -134,71 +153,60 @@ class Leadership(OptionDescription): values.reset(soption_bag) def follower_force_store_value(self, - values, - value, - option_bag, - owner, - dyn=None, - ) -> None: - settings = option_bag.config_bag.context.get_settings() + value, + config_bag: 'ConfigBag', + owner, + dyn=None, + ) -> None: + """apply force_store_value to follower + """ if value: if dyn is None: 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, None, - option_bag.config_bag, + config_bag, ) - if 'force_store_value' in foption_bag.properties: - if idx == 0: - indexes = [None] - else: - indexes = range(len(value)) - for index in indexes: - foption_bag_index = OptionBag(follower, - index, - option_bag.config_bag, - ) - values.set_storage_value(foption_bag_index.path, - index, - values.getvalue(foption_bag_index), - owner, - ) + if 'force_store_value' not in foption_bag.properties: + continue + if idx == 0: + indexes = [None] + else: + indexes = range(len(value)) + for index in indexes: + foption_bag_index = OptionBag(follower, + index, + config_bag, + ) + values.set_storage_value(foption_bag_index.path, + index, + values.get_value(foption_bag_index), + owner, + ) def pop(self, - values: Values, index: int, - option_bag: OptionBag, + config_bag: 'ConfigBag', followers: Optional[List[Option]]=undefined, ) -> None: + """pop leader value and follower's one + """ if followers is undefined: # followers are not undefined only in SynDynLeadership followers = self.get_followers() - config_bag = option_bag.config_bag.copy() + config_bag = config_bag.copy() config_bag.remove_validation() + values = config_bag.context.get_values() for follower in followers: - follower_path = follower.impl_getpath() - followerlen = values.get_max_length(follower_path) soption_bag = OptionBag(follower, index, 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, - 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, - ) + values.reduce_index(soption_bag) def reset_cache(self, path: str, @@ -221,17 +229,16 @@ class Leadership(OptionDescription): ) -> None: super().reset_cache(path, config_bag, - resetted_opts) + resetted_opts, + ) leader.reset_cache(leader.impl_getpath(), config_bag, None) for follower in followers: - spath = follower.impl_getpath() - follower.reset_cache(spath, + follower.reset_cache(follower.impl_getpath(), config_bag, - None) - # do not reset dependencies option - # resetted_opts.append(spath) + None, + ) def impl_is_leadership(self) -> None: return True diff --git a/tiramisu/option/macoption.py b/tiramisu/option/macoption.py index d5097a3..57dbd8a 100644 --- a/tiramisu/option/macoption.py +++ b/tiramisu/option/macoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""MACOption +""" import re from ..i18n import _ @@ -25,7 +27,8 @@ from .stroption import RegexpOption class MACOption(RegexpOption): + """represents the choice of a mac address + """ __slots__ = tuple() _regexp = re.compile(r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$") - _type = 'macaddress' - _display_name = _('mac address') + _type = _('mac address') diff --git a/tiramisu/option/netmaskoption.py b/tiramisu/option/netmaskoption.py index 47e893c..43785b1 100644 --- a/tiramisu/option/netmaskoption.py +++ b/tiramisu/option/netmaskoption.py @@ -18,21 +18,18 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from ipaddress import ip_interface, ip_network -from typing import List - -from ..error import ConfigError -from ..setting import undefined, OptionBag, Undefined +"""NetmaskOption +""" +from ipaddress import ip_network from ..i18n import _ -from .option import Option from .stroption import StrOption class NetmaskOption(StrOption): - "represents the choice of a netmask" + """represents the choice of a netmask + """ __slots__ = tuple() - _type = 'netmask' - _display_name = _('netmask address') + _type = _('netmask address') def validate(self, value: str) -> None: @@ -41,6 +38,6 @@ class NetmaskOption(StrOption): if val.startswith("0") and len(val) > 1: raise ValueError() try: - ip_network('0.0.0.0/{0}'.format(value)) - except ValueError: - raise ValueError() + ip_network(f'0.0.0.0/{value}') + except ValueError as err: + raise ValueError() from err diff --git a/tiramisu/option/networkoption.py b/tiramisu/option/networkoption.py index caf85c3..4787799 100644 --- a/tiramisu/option/networkoption.py +++ b/tiramisu/option/networkoption.py @@ -18,7 +18,9 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # 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 .stroption import StrOption @@ -27,8 +29,7 @@ from .stroption import StrOption class NetworkOption(StrOption): "represents the choice of a network" __slots__ = tuple() - _type = 'network' - _display_name = _('network address') + _type = _('network address') def __init__(self, *args, @@ -56,8 +57,8 @@ class NetworkOption(StrOption): raise ValueError() try: ip_network(value) - except ValueError: - raise ValueError() + except ValueError as err: + raise ValueError() from err def second_level_validation(self, value: str, diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index e9c8008..ba2b998 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -20,21 +20,19 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import warnings -import weakref -from typing import Any, List, Callable, Optional, Dict, Union, Tuple +from typing import Any, List, Optional, Dict from itertools import chain -from .baseoption import BaseOption, submulti, STATIC_TUPLE +from .baseoption import BaseOption, submulti from ..i18n import _ -from ..setting import undefined, OptionBag, Undefined -from ..autolib import Calculation, Params, ParamOption, ParamInformation, ParamSelfInformation -from ..error import (ConfigError, ValueWarning, ValueErrorWarning, - ValueOptionError, display_list) +from ..setting import undefined, OptionBag +from ..autolib import Calculation, ParamOption, ParamInformation, ParamSelfInformation +from ..error import ValueWarning, ValueErrorWarning, ValueOptionError from .syndynoption import SynDynOption -#ALLOWED_CONST_LIST = ['_cons_not_equal'] class Option(BaseOption): + # pylint: disable=too-many-statements,too-many-branches,too-many-arguments,too-many-locals """ Abstract base class for configuration option's. @@ -55,7 +53,7 @@ class Option(BaseOption): '_choice_values', '_choice_values_params', ) - _empty = '' + _type = None def __init__(self, name: str, doc: str, @@ -99,7 +97,7 @@ class Option(BaseOption): is_multi=is_multi) if validators is not None: 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: if __debug__ and not isinstance(validator, Calculation): raise ValueError(_('validators must be a Calculation for "{}"').format(name)) @@ -128,19 +126,20 @@ class Option(BaseOption): ) try: self.validate(value) - self.sync_validate_with_option(value, - option_bag) + self.validate_with_option(value, + option_bag, + loaded=True, + ) except ValueError as err: str_err = str(err) if not str_err: raise ValueError(_('invalid default_multi value "{0}" ' 'for option "{1}"').format(str(value), - self.impl_get_display_name())) - else: - raise ValueError(_('invalid default_multi value "{0}" ' - 'for option "{1}", {2}').format(str(value), - self.impl_get_display_name(), - str_err)) + self.impl_get_display_name()) + ) from err + raise ValueError(_(f'invalid default_multi value "{value}" for option ' + f'"{self.impl_get_display_name()}", {str_err}') + ) from err if _multi is submulti: if not isinstance(default_multi, Calculation): if not isinstance(default_multi, list): @@ -158,11 +157,15 @@ class Option(BaseOption): undefined, properties=None, ) - self.sync_impl_validate(default, - option_bag) - self.sync_impl_validate(default, - option_bag, - check_error=False) + self.impl_validate(default, + option_bag, + loaded=True, + ) + self.impl_validate(default, + option_bag, + check_error=False, + loaded=True, + ) self.value_dependencies(default, _dependencies_information) if (is_multi and default != []) or \ (not is_multi and default is not None): @@ -176,6 +179,8 @@ class Option(BaseOption): value: Any, _dependencies_information: List[str], ) -> Any: + """parse dependancies to add dependencies + """ if isinstance(value, list): for val in value: if isinstance(value, list): @@ -189,8 +194,11 @@ class Option(BaseOption): value: Any, _dependencies_information: List[str], ) -> Any: + """parse dependancy to add dependencies + """ for param in chain(value.params.args, value.params.kwargs.values()): if isinstance(param, ParamOption): + # pylint: disable=protected-access param.option._add_dependency(self) elif isinstance(param, ParamSelfInformation): _dependencies_information[1].append(param.information_name) @@ -201,23 +209,28 @@ class Option(BaseOption): # option's information def impl_is_multi(self) -> bool: + """is it a multi option + """ return getattr(self, '_multi', 1) != 1 def impl_is_submulti(self) -> bool: + """is it a submulti option + """ return getattr(self, '_multi', 1) == 2 def impl_is_dynsymlinkoption(self) -> bool: + """is a dynsymlinkoption? + """ return False def get_type(self) -> str: - # _display_name for compatibility with older version than 3.0rc3 - return getattr(self, '_type', self._display_name) - - def get_display_type(self) -> str: - return self._display_name + """get the type of option + """ + return self._type def impl_getdefault(self) -> Any: - "accessing the default value" + """accessing the default value + """ is_multi = self.impl_is_multi() default = getattr(self, '_default', undefined) if default is undefined: @@ -225,13 +238,13 @@ class Option(BaseOption): default = [] else: default = None - else: - if is_multi and isinstance(default, list): - default = list(default) + elif is_multi and isinstance(default, tuple): + default = list(default) return default 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(): default_value = [] else: @@ -239,93 +252,25 @@ class Option(BaseOption): return getattr(self, '_default_multi', default_value) def impl_get_extra(self, - key: str) -> Any: + key: str, + ) -> Any: + """if extra parameters are store get it + """ extra = getattr(self, '_extra', {}) if isinstance(extra, tuple): if key in extra[0]: return extra[1][extra[0].index(key)] return None - else: - return extra.get(key) + return extra.get(key) #__________________________________________________________________________ # 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, - value: Any, - option_bag: OptionBag, - check_error: bool=True) -> None: + value: Any, + option_bag: OptionBag, + check_error: bool=True, + loaded: bool=False, + ) -> None: """Return True if value is really valid If not validate or invalid return it returns False """ @@ -337,7 +282,6 @@ class Option(BaseOption): not 'validator' in config_bag.properties: return False - def _is_not_unique(value, option_bag): # if set(value) has not same length than value if config_bag is undefined or not check_error or \ @@ -353,9 +297,11 @@ class Option(BaseOption): '').format(val)) def calculation_validator(val, - _index): + _index, + ): 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 (not check_error and calc_is_warnings_only)): try: @@ -374,26 +320,32 @@ class Option(BaseOption): **kwargs) except ValueWarning as warn: warnings.warn_explicit(ValueWarning(val, - self._display_name, + self.get_type(), self, - '{0}'.format(warn), + str(warn), _index), ValueWarning, - self.__class__.__name__, 356) + self.__class__.__name__, 319) def do_validation(_value, - _index): + _index, + ): + # if isinstance(_value, list): 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: - return False + return + if _value is not None: if check_error: # option validation self.validate(_value) self.validate_with_option(_value, - option_bag) + option_bag, + loaded=loaded, + ) if ((check_error and not is_warnings_only) or (not check_error and is_warnings_only)): try: @@ -402,16 +354,18 @@ class Option(BaseOption): except ValueError as err: if is_warnings_only: warnings.warn_explicit(ValueWarning(_value, - self._display_name, + self.get_type(), self, - '{0}'.format(err), + str(err), _index), ValueWarning, self.__class__.__name__, 0) else: raise err - calculation_validator(_value, - _index) + if not loaded: + calculation_validator(_value, + _index, + ) try: val = value err_index = force_index @@ -423,11 +377,15 @@ class Option(BaseOption): raise ValueError(_('which must be a list')) for val in value: do_validation(val, - force_index) - _is_not_unique(value, option_bag) + force_index, + ) + _is_not_unique(value, + option_bag, + ) else: do_validation(val, - force_index) + force_index, + ) elif isinstance(value, Calculation) and config_bag is undefined: pass elif not isinstance(value, list): @@ -444,73 +402,68 @@ class Option(BaseOption): err_index) _is_not_unique(lval, option_bag) else: - # FIXME subtimal, not several time is whole=True! + # FIXME suboptimal, not several time is whole=True! for err_index, val in enumerate(value): do_validation(val, - err_index) + err_index, + ) _is_not_unique(value, option_bag) except ValueError as err: if config_bag is undefined or \ 'demoting_error_warning' not in config_bag.properties: raise ValueOptionError(val, - self._display_name, + self.get_type(), option_bag.ori_option, - '{0}'.format(err), + str(err), err_index) from err warnings.warn_explicit(ValueErrorWarning(val, - self._display_name, + self.get_type(), option_bag.ori_option, - '{0}'.format(err), + str(err), err_index), ValueErrorWarning, self.__class__.__name__, 0) return False 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, value: Any, - option_bag: OptionBag) -> None: - pass + option_bag: OptionBag, + loaded: bool, + ) -> None: + """validation function with option + """ def second_level_validation(self, value: Any, - warnings_only: bool) -> None: - pass + warnings_only: bool, + ) -> None: + """less import validation function + """ def impl_is_leader(self): + """check if option is a leader in a leadership + """ leadership = self.impl_get_leadership() if leadership is None: return False return leadership.is_leader(self) def impl_is_follower(self): + """check if option is a leader in a follower + """ leadership = self.impl_get_leadership() if leadership is None: return False return not leadership.is_leader(self) def impl_get_leadership(self): + """get leadership + """ leadership = getattr(self, '_leadership', None) if leadership is None: return leadership + #pylint: disable=not-callable return leadership() def to_dynoption(self, @@ -518,8 +471,14 @@ class Option(BaseOption): suffix: str, dyn_parent, ) -> SynDynOption: + """tranforme a dynoption to a syndynoption + """ return SynDynOption(self, rootpath, suffix, dyn_parent, ) + def validate(self, value: Any): + """option needs a validate function + """ + raise NotImplementedError() diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 17d3798..d6f18ee 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -18,27 +18,31 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from copy import copy +"""OptionDescription +""" from typing import Optional, Iterator, Union, List from ..i18n import _ from ..setting import ConfigBag, OptionBag, groups, undefined, owners, Undefined from .baseoption import BaseOption -from .syndynoptiondescription import SynDynOptionDescription, SynDynLeadership +from .syndynoptiondescription import SynDynOptionDescription from ..error import ConfigError, ConflictError class CacheOptionDescription(BaseOption): + """manage cache for option description + """ __slots__ = ('_cache_force_store_values', '_cache_dependencies_information', ) def impl_already_build_caches(self) -> bool: + """is a readonly option? + """ return self.impl_is_readonly() def _build_cache(self, - path='', _consistencies=None, _consistencies_id=0, currpath: List[str]=None, @@ -49,6 +53,7 @@ class CacheOptionDescription(BaseOption): ) -> None: """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 if _consistencies is None: init = True @@ -65,15 +70,16 @@ class CacheOptionDescription(BaseOption): # cache already set raise ConfigError(_('option description seems to be part of an other ' 'config')) - for option in self.get_children(config_bag=undefined, - dyn=False): + for option in self.get_children(config_bag=undefined, # pylint: disable=no-member + dyn=False, + ): if __debug__: cache_option.append(option) sub_currpath = currpath + [option.impl_getname()] subpath = '.'.join(sub_currpath) if isinstance(option, OptionDescription): - option._build_cache(subpath, - _consistencies, + # pylint: disable=protected-access + option._build_cache(_consistencies, _consistencies_id, sub_currpath, cache_option, @@ -84,7 +90,6 @@ class CacheOptionDescription(BaseOption): else: for information in option.get_dependencies_information(): dependencies_information.setdefault(information, []).append(option) - is_multi = option.impl_is_multi() if not option.impl_is_symlinkoption(): properties = option.impl_getproperties() if 'force_store_value' in properties: @@ -101,18 +106,21 @@ class CacheOptionDescription(BaseOption): if option.impl_is_readonly(): raise ConflictError(_('duplicate option: {0}').format(option)) if not self.impl_is_readonly() and display_name: - option._display_name_function = display_name - option._path = subpath - option._set_readonly() + option._display_name_function = display_name # pylint: disable=protected-access + option._path = subpath # pylint: disable=protected-access + option._set_readonly() # pylint: disable=protected-access if init: - self._cache_force_store_values = force_store_values - self._cache_dependencies_information = dependencies_information - self._path = self._name + self._cache_force_store_values = force_store_values # pylint: disable=attribute-defined-outside-init + self._cache_dependencies_information = dependencies_information # pylint: disable=attribute-defined-outside-init + self._path = self._name # pylint: disable=attribute-defined-outside-init,no-member self._set_readonly() def impl_build_force_store_values(self, config_bag: ConfigBag, ) -> None: + """set value to force_store_values option + """ + # pylint: disable=too-many-branches def do_option_bags(option): if option.issubdyn(): dynopt = option.getsubdyn() @@ -146,7 +154,7 @@ class CacheOptionDescription(BaseOption): leader = option.impl_get_leadership().get_leader() for leader_option_bag in do_option_bags(leader): 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(): subpath = leader_option_bag.option.rootpath doption = option.to_dynoption(subpath, @@ -164,7 +172,7 @@ class CacheOptionDescription(BaseOption): config_bag, properties=frozenset(), ) - value = values.getvalue(option_bag) + value = values.get_value(option_bag) if value is None: continue values.set_storage_value(subpath, @@ -175,7 +183,7 @@ class CacheOptionDescription(BaseOption): else: for option_bag in do_option_bags(option): option_bag.properties = frozenset() - value = values.getvalue(option_bag) + value = values.get_value(option_bag) if value is None: continue if values.hasvalue(option_bag.option.impl_getpath()): @@ -188,6 +196,8 @@ class CacheOptionDescription(BaseOption): class OptionDescriptionWalk(CacheOptionDescription): + """get child of option description + """ __slots__ = ('_children',) def get_child(self, @@ -195,16 +205,18 @@ class OptionDescriptionWalk(CacheOptionDescription): config_bag: ConfigBag, subpath: str, ) -> Union[BaseOption, SynDynOptionDescription]: + """get a child + """ # if not dyn - if name in self._children[0]: - option = self._children[1][self._children[0].index(name)] + if name in self._children[0]: # pylint: disable=no-member + option = self._children[1][self._children[0].index(name)] # pylint: disable=no-member if option.issubdyn(): raise AttributeError(_(f'unknown option "{name}" ' "in root optiondescription (it's a dynamic option)" )) return option # if dyn - for child in self._children[1]: + for child in self._children[1]: # pylint: disable=no-member if not child.impl_is_dynoptiondescription(): continue cname = child.impl_getname() @@ -217,7 +229,7 @@ class OptionDescriptionWalk(CacheOptionDescription): suffix, 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}" ' 'in root optiondescription' )) @@ -229,21 +241,22 @@ class OptionDescriptionWalk(CacheOptionDescription): config_bag: Union[ConfigBag, Undefined], dyn: bool=True, ) -> Union[BaseOption, SynDynOptionDescription]: + """get children + """ if not dyn or config_bag is undefined or \ config_bag.context.get_description() == self: subpath = '' else: subpath = self.impl_getpath() - children = [] - for child in self._children[1]: + for child in self._children[1]: # pylint: disable=no-member if dyn and child.impl_is_dynoptiondescription(): for suffix in child.get_suffixes(config_bag): - children.append(child.to_dynoption(subpath, - suffix, - child)) + yield child.to_dynoption(subpath, + suffix, + child, + ) else: - children.append(child) - return children + yield child def get_children_recursively(self, bytype: Optional[BaseOption], @@ -251,6 +264,8 @@ class OptionDescriptionWalk(CacheOptionDescription): config_bag: ConfigBag, self_opt: BaseOption=None, ) -> Iterator[Union[BaseOption, SynDynOptionDescription]]: + """get children recursively + """ if self_opt is None: self_opt = self for option in self_opt.get_children(config_bag): @@ -308,8 +323,8 @@ class OptionDescription(OptionDescriptionWalk): if dynopt_names: for dynopt in dynopt_names: if child != dynopt and child.startswith(dynopt): - raise ConflictError(_('the option\'s name "{}" start as ' - 'the dynoptiondescription\'s name "{}"').format(child, dynopt)) + raise ConflictError(_(f'the option\'s name "{child}" start as ' + f'the dynoptiondescription\'s name "{dynopt}"')) old = child self._children = children_ # the group_type is useful for filtering OptionDescriptions in a config @@ -322,17 +337,24 @@ class OptionDescription(OptionDescriptionWalk): 'dynoptiondescription')) def impl_is_optiondescription(self) -> bool: + """the option is an option description + """ return True def impl_is_dynoptiondescription(self) -> bool: + """the option is not dynamic + """ return False def impl_is_leadership(self) -> bool: + """the option is not a leadership + """ return False # ____________________________________________________________ def impl_set_group_type(self, - group_type: groups.GroupType) -> None: + group_type: groups.GroupType, + ) -> None: """sets a given group object to an OptionDescription :param group_type: an instance of `GroupType` or `LeadershipGroupType` @@ -351,16 +373,22 @@ class OptionDescription(OptionDescriptionWalk): self._group_type = group_type def impl_get_group_type(self) -> groups.GroupType: + """get the group type of option description + """ return self._group_type def to_dynoption(self, rootpath: str, suffix: str, ori_dyn) -> SynDynOptionDescription: + """get syn dyn option description + """ return SynDynOptionDescription(self, rootpath, suffix, ori_dyn) def impl_is_dynsymlinkoption(self) -> bool: + """option is not a dyn symlink option + """ return False diff --git a/tiramisu/option/passwordoption.py b/tiramisu/option/passwordoption.py index 042c66b..05fdf2a 100644 --- a/tiramisu/option/passwordoption.py +++ b/tiramisu/option/passwordoption.py @@ -18,15 +18,15 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""PasswordOption +""" -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ -from .option import Option from .stroption import StrOption class PasswordOption(StrOption): - "represents the choice of a password" + """represents the choice of a password + """ __slots__ = tuple() - _type = 'password' - _display_name = _('password') + _type = _('password') diff --git a/tiramisu/option/permissionsoption.py b/tiramisu/option/permissionsoption.py index 5d07ae3..b156786 100644 --- a/tiramisu/option/permissionsoption.py +++ b/tiramisu/option/permissionsoption.py @@ -18,12 +18,11 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""PermissionsOption +""" import re -import sys -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ -from .option import Option from .intoption import IntOption @@ -36,8 +35,7 @@ class PermissionsOption(IntOption): """ __slots__ = tuple() perm_re = re.compile(r"^[0-7]{3,4}$") - _type = 'permissions' - _display_name = _('unix file permissions') + _type = _('unix file permissions') def __init__(self, *args, @@ -72,4 +70,4 @@ class PermissionsOption(IntOption): raise ValueError(_(f'{new} has more right than {old}')) old_digit = new_digit if str_value == '777': - raise ValueError(_(f'too weak')) + raise ValueError(_('too weak')) diff --git a/tiramisu/option/portoption.py b/tiramisu/option/portoption.py index 19752e1..7ca1f71 100644 --- a/tiramisu/option/portoption.py +++ b/tiramisu/option/portoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""PortOption +""" import re from ..i18n import _ @@ -36,8 +38,7 @@ class PortOption(StrOption): """ __slots__ = tuple() port_re = re.compile(r"^[0-9]*$") - _type = 'port' - _display_name = _('port') + _port = _('port') def __init__(self, *args, @@ -81,7 +82,8 @@ class PortOption(StrOption): def validate(self, value: str) -> None: 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:]] elif self.impl_get_extra('_allow_range') and ":" in str(value): value = value.split(':') @@ -100,7 +102,8 @@ class PortOption(StrOption): def second_level_validation(self, value: str, 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:]] elif ':' in value: value = value.split(':') diff --git a/tiramisu/option/stroption.py b/tiramisu/option/stroption.py index 4c92add..785873e 100644 --- a/tiramisu/option/stroption.py +++ b/tiramisu/option/stroption.py @@ -18,31 +18,40 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -import sys +"""StrOption and RegexpOption +""" from typing import Any -from ..setting import undefined, Undefined, OptionBag from ..i18n import _ from .option import Option class StrOption(Option): - "represents the choice of a string" + """represents a string + """ __slots__ = tuple() - _type = 'string' - _display_name = _('string') + _type = _('string') def validate(self, - value: str) -> None: + value: str, + ) -> None: + """validation + """ if not isinstance(value, str): raise ValueError() class RegexpOption(StrOption): + """regexp validation, this is base option use to do a custom's one + """ __slots__ = tuple() def validate(self, - value: Any) -> None: + value: Any, + ) -> None: + # pylint: disable=no-member + """validation + """ super().validate(value) match = self._regexp.search(value) if not match: diff --git a/tiramisu/option/symlinkoption.py b/tiramisu/option/symlinkoption.py index b55fe70..3526f6b 100644 --- a/tiramisu/option/symlinkoption.py +++ b/tiramisu/option/symlinkoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""SymLinkOption link to an other option +""" from typing import Any from .baseoption import BaseOption, valid_name from ..error import ConfigError @@ -25,11 +27,15 @@ from ..i18n import _ class SymLinkOption(BaseOption): + """SymLinkOption link to an other option + """ __slots__ = ('_opt',) def __init__(self, name: str, - opt: BaseOption) -> None: + opt: BaseOption, + ) -> None: + # pylint: disable=super-init-not-called if not valid_name(name): raise ValueError(_('"{0}" is an invalid name for an option').format(name)) if not isinstance(opt, BaseOption) or \ @@ -55,27 +61,36 @@ class SymLinkOption(BaseOption): 'dynoptiondescription')) 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 False, cannot has validation or callback, so return False """ return self_is_dep def impl_is_symlinkoption(self) -> bool: + """it's a symlinkoption + """ return True def impl_getopt(self) -> BaseOption: + """get to linked option + """ return self._opt def issubdyn(self) -> bool: + """it's not a sub dyn option + """ return False def impl_is_multi(self) -> bool: + """is it a multi? + """ if self._opt.issubdyn(): return True return self._opt.impl_is_multi() def impl_is_submulti(self) -> bool: - if self._opt.issubdyn() and self._opt.impl_is_multi(): - return True + """is it a submulti? + """ return self._opt.impl_is_submulti() diff --git a/tiramisu/option/syndynoption.py b/tiramisu/option/syndynoption.py index 8f1fbc2..17c616d 100644 --- a/tiramisu/option/syndynoption.py +++ b/tiramisu/option/syndynoption.py @@ -18,8 +18,9 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""SynDynOption internal option, it's an instanciate synoption +""" from typing import Any -from ..setting import undefined, OptionBag from .baseoption import BaseOption @@ -49,35 +50,40 @@ class SynDynOption: 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: + """get option name + """ return self.opt.impl_getname() + self.dyn_parent.convert_suffix_to_path(self.suffix) def impl_get_display_name(self) -> str: - return self.opt._get_display_name(dyn_name=self.impl_getname(), - suffix=self.dyn_parent.convert_suffix_to_path(self.suffix), + """get option display name + """ + 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: + """get suffix + """ return self.suffix def impl_getpath(self) -> str: + """get path + """ path = self.impl_getname() if self.rootpath: path = f'{self.rootpath}.{path}' return path def impl_is_dynsymlinkoption(self) -> bool: + """it's a dynsymlinkoption + """ 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() if leadership: rootpath = self.rootpath.rsplit('.', 1)[0] diff --git a/tiramisu/option/syndynoptiondescription.py b/tiramisu/option/syndynoptiondescription.py index f7cd316..ae2cf50 100644 --- a/tiramisu/option/syndynoptiondescription.py +++ b/tiramisu/option/syndynoptiondescription.py @@ -18,17 +18,21 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # 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 ..i18n import _ -from ..setting import ConfigBag, groups, undefined, Settings -from ..value import Values +from ..setting import ConfigBag from .baseoption import BaseOption from .syndynoption import SynDynOption class SynDynOptionDescription: + """SynDynOptionDescription internal option, it's an instanciate synoptiondescription + """ __slots__ = ('opt', 'rootpath', '_suffix', @@ -53,14 +57,14 @@ class SynDynOptionDescription: name, ) - def impl_getopt(self) -> BaseOption: - return self.opt - def get_child(self, name: str, config_bag: ConfigBag, subpath: str, ) -> BaseOption: + """get child by name + """ + # pylint: disable=unused-argument suffix = self.ori_dyn.convert_suffix_to_path(self._suffix) if name.endswith(suffix): oname = name[:-len(suffix)] @@ -78,15 +82,17 @@ class SynDynOptionDescription: '').format(name, self.impl_get_display_name())) def impl_getname(self) -> str: + """get name + """ 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, config_bag: ConfigBag, dyn: bool=True, ): + # pylint: disable=unused-argument + """get children + """ subpath = self.impl_getpath() children = [] for child in self.opt.get_children(config_bag): @@ -97,6 +103,8 @@ class SynDynOptionDescription: return children def impl_is_dynsymlinkoption(self) -> bool: + """it's a dynsymlinkoption + """ return True def get_children_recursively(self, @@ -105,6 +113,9 @@ class SynDynOptionDescription: config_bag: ConfigBag, self_opt: BaseOption=None, ) -> BaseOption: + # pylint: disable=unused-argument + """get children recursively + """ for option in self.opt.get_children_recursively(bytype, byname, config_bag, @@ -113,23 +124,33 @@ class SynDynOptionDescription: yield option def impl_getpath(self) -> str: + """get path + """ path = self.impl_getname() if self.rootpath: path = f'{self.rootpath}.{path}' return path def impl_get_display_name(self) -> str: + """get display name + """ return self.opt.impl_get_display_name() + str(self._suffix) class SynDynLeadership(SynDynOptionDescription): + """SynDynLeadership internal option, it's an instanciate synoptiondescription + """ def get_leader(self) -> SynDynOption: + """get the leader + """ return self.opt.get_leader().to_dynoption(self.impl_getpath(), self._suffix, self.ori_dyn, ) def get_followers(self) -> Iterator[SynDynOption]: + """get followers + """ subpath = self.impl_getpath() for follower in self.opt.get_followers(): yield follower.to_dynoption(subpath, @@ -142,6 +163,8 @@ class SynDynLeadership(SynDynOptionDescription): config_bag: 'ConfigBag', resetted_opts: List[str], ) -> None: + """reset cache + """ leader = self.get_leader() followers = self.get_followers() self._reset_cache(path, @@ -155,23 +178,27 @@ class SynDynLeadership(SynDynOptionDescription): *args, **kwargs, ) -> None: + """pop value for a follower + """ self.opt.pop(*args, followers=self.get_followers(), **kwargs, ) def follower_force_store_value(self, - values, value, - option_bag, + config_bag, owner, ) -> None: - self.opt.follower_force_store_value(values, - value, - option_bag, + """force store value for a follower + """ + self.opt.follower_force_store_value(value, + config_bag, owner, dyn=self, ) def impl_getsuffix(self) -> str: + """get suffix + """ return self._suffix diff --git a/tiramisu/option/urloption.py b/tiramisu/option/urloption.py index 8c14bfc..31bbb8f 100644 --- a/tiramisu/option/urloption.py +++ b/tiramisu/option/urloption.py @@ -18,22 +18,25 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""URLOption to check url value +""" import re from typing import Any, Optional, List, Dict -from ..setting import undefined, Undefined, OptionBag +from ..setting import undefined from ..i18n import _ -from .option import Option, Calculation +from .option import Calculation from .stroption import StrOption from .domainnameoption import DomainnameOption from .portoption import PortOption class URLOption(StrOption): + """URLOption to check url value + """ __slots__ = tuple() path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") - _type = 'url' - _display_name = _('URL') + _type = _('URL') def __init__(self, name: str, @@ -53,8 +56,7 @@ class URLOption(StrOption): allow_wellknown: bool=True, allow_registred: bool=True, allow_private: bool=False) -> None: - - + # pylint: disable=too-many-arguments,too-many-locals,redefined-builtin extra = {'_domainname': DomainnameOption(name, doc, allow_ip=allow_ip, @@ -80,10 +82,10 @@ class URLOption(StrOption): def _get_domain_port_files(self, value: str) -> (str, str): if value.startswith('http://'): - type = 'http' + type_ = 'http' value = value[7:] elif value.startswith('https://'): - type = 'https' + type_ = 'https' value = value[8:] else: raise ValueError(_('must start with http:// or ' @@ -100,7 +102,7 @@ class URLOption(StrOption): if len(splitted) == 1: domain = splitted[0] port = {'http': '80', - 'https': '443'}[type] + 'https': '443'}[type_] else: domain, port = splitted return domain, port, files @@ -120,7 +122,7 @@ class URLOption(StrOption): raise ValueError(_('must ends with a valid resource name')) 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 portoption = self.impl_get_extra('_port') portoption.second_level_validation(port, warnings_only) diff --git a/tiramisu/option/usernameoption.py b/tiramisu/option/usernameoption.py index 1d846c0..89d5066 100644 --- a/tiramisu/option/usernameoption.py +++ b/tiramisu/option/usernameoption.py @@ -18,6 +18,8 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"""UsernameOption or GroupnameOption to check unix username/group value +""" import re from ..i18n import _ @@ -25,14 +27,16 @@ from .stroption import RegexpOption class UsernameOption(RegexpOption): + """UsernameOption to check unix username value + """ __slots__ = tuple() #regexp build with 'man 8 adduser' informations _regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") - _type = 'username' - _display_name = _('unix username') + _type = _('unix username') class GroupnameOption(UsernameOption): + """GroupnameOption to check unix group value + """ __slots__ = tuple() - _type = 'groupname' - _display_name = _('unix groupname') + _type = _('unix groupname') diff --git a/tiramisu/setting.py b/tiramisu/setting.py index e5fd0f1..22751a2 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -15,94 +15,92 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from typing import Union +from typing import Union, Set from itertools import chain -from .error import PropertiesOptionError, ConstError, ConfigError, LeadershipError, display_list +from .error import PropertiesOptionError, ConstError, ConfigError, LeadershipError from .i18n import _ -"""If cache and expire is enable, time before cache is expired. -This delay start first time value/setting is set in cache, even if -user access several time to value/setting -""" +# If cache and expire is enable, time before cache is expired. +# This delay start first time value/setting is set in cache, even if +# user access several time to value/setting 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 -an Option and for the Config together, Setting raise a PropertiesOptionError - -* Common properties: - -hidden - option with this property can only get value in read only mode. This - option is not available in read write mode. - -disabled - option with this property cannot be set/get - -frozen - cannot set value for option with this properties if 'frozen' is set in - config - -* Special property: - -permissive - option with 'permissive' cannot raise PropertiesOptionError for properties - set in permissive - config with 'permissive', whole option in this config cannot raise - PropertiesOptionError for properties set in permissive - -mandatory - should set value for option with this properties if 'mandatory' is set in - config - example: 'a', ['a'], [None] are valid - None, [] are not valid - -empty - raise mandatory PropertiesOptionError if multi or leader have empty value - example: ['a'] is valid - [None] is not valid - -unique - raise ValueError if a value is set twice or more in a multi Option - -* Special Config properties: - -cache - if set, enable cache settings and values - -expire - if set, settings and values in cache expire after ``expiration_time`` - -everything_frozen - whole option in config are frozen (even if option have not frozen - property) - -validator - launch validator set by user in option (this property has no effect - for internal validator) - -warnings - display warnings during validation - -demoting_error_warning - all value errors are convert to warning (ValueErrorWarning) -""" +#List of default properties (you can add new one if needed). +# +#For common properties and personalise properties, if a propery is set for +#an Option and for the Config together, Setting raise a PropertiesOptionError +# +#* Common properties: +# +#hidden +# option with this property can only get value in read only mode. This +# option is not available in read write mode. +# +#disabled +# option with this property cannot be set/get +# +#frozen +# cannot set value for option with this properties if 'frozen' is set in +# config +# +#* Special property: +# +#permissive +# option with 'permissive' cannot raise PropertiesOptionError for properties +# set in permissive +# config with 'permissive', whole option in this config cannot raise +# PropertiesOptionError for properties set in permissive +# +#mandatory +# should set value for option with this properties if 'mandatory' is set in +# config +# example: 'a', ['a'], [None] are valid +# None, [] are not valid +# +#empty +# raise mandatory PropertiesOptionError if multi or leader have empty value +# example: ['a'] is valid +# [None] is not valid +# +#unique +# raise ValueError if a value is set twice or more in a multi Option +# +#* Special Config properties: +# +#cache +# if set, enable cache settings and values +# +#expire +# if set, settings and values in cache expire after ``expiration_time`` +# +#everything_frozen +# whole option in config are frozen (even if option have not frozen +# property) +# +#validator +# launch validator set by user in option (this property has no effect +# for internal validator) +# +#warnings +# display warnings during validation +# +#demoting_error_warning +# all value errors are convert to warning (ValueErrorWarning) DEFAULT_PROPERTIES = frozenset(['cache', 'validator', 'warnings']) SPECIAL_PROPERTIES = {'frozen', 'mandatory', 'empty', 'force_store_value'} -"""Config can be in two defaut mode: - -read_only - you can get all variables not disabled but you cannot set any variables - if a value has a callback without any value, callback is launch and value - of this variable can change - you cannot access to mandatory variable without values - -read_write - you can get all variables not disabled and not hidden - you can set all variables not frozen -""" +#Config can be in two defaut mode: +# +#read_only +# you can get all variables not disabled but you cannot set any variables +# if a value has a callback without any value, callback is launch and value +# of this variable can change +# you cannot access to mandatory variable without values +# +#read_write +# you can get all variables not disabled and not hidden +# you can set all variables not frozen RO_APPEND = frozenset(['frozen', 'disabled', '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 return 'Undefined' @@ -158,6 +159,8 @@ undefined = Undefined() class OptionBag: + """Object to store information for an option + """ __slots__ = ('option', # current option 'path', 'index', @@ -167,6 +170,7 @@ class OptionBag: 'apply_requires', # apply requires or not for this option ) + # pylint: disable=too-many-arguments def __init__(self, option, index, @@ -187,42 +191,39 @@ class OptionBag: self.path = path elif option: 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 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: self.properties = properties def __getattr__(self, key): if key == 'ori_option': return self.option - elif key == 'apply_requires': + if key == 'apply_requires': return True - return undefined - - 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 + return None def copy(self): + """copy OptionBag + """ option_bag = OptionBag(None, None, None, ) for key in self.__slots__: - if not hasattr(self, key): - continue setattr(option_bag, key, getattr(self, key)) return option_bag class ConfigBag: + """Object to store information for context + """ __slots__ = ('context', # link to the current context 'properties', # properties for current context 'true_properties', # properties for current context @@ -246,30 +247,37 @@ class ConfigBag: if key == 'true_properties': return self.properties if key == 'expiration_time': - self.expiration_time = EXPIRATION_TIME + self.expiration_time = EXPIRATION_TIME # pylint: disable=attribute-defined-outside-init return self.expiration_time if key == 'is_unrestraint': 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): - super().__setattr__(key, value) - - def remove_warnings(self): + def nowarnings(self): + """do not warnings + """ self.properties = frozenset(self.properties - {'warnings'}) def remove_validation(self): + """do not validate option + """ self.properties = frozenset(self.properties - {'validator'}) def unrestraint(self): - self.is_unrestraint = True - self.true_properties = self.properties + """do not restraint access to option + """ + 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']) def set_permissive(self): + """set permissive + """ self.properties = frozenset(self.properties | {'permissive'}) def copy(self): + """copy the config + """ kwargs = {} for key in self.__slots__: kwargs[key] = getattr(self, key) @@ -277,7 +285,7 @@ class ConfigBag: # ____________________________________________________________ -class _NameSpace(object): +class _NameSpace: """convenient class that emulates a module and builds constants (that is, unique names) when attribute is added, we cannot delete it @@ -299,28 +307,27 @@ class _NameSpace(object): class GroupModule(_NameSpace): "emulates a module to manage unique group (OptionDescription) names" + # pylint: disable=too-few-public-methods class GroupType(str): """allowed normal group (OptionDescription) names *normal* means : groups that are not leader """ - pass class DefaultGroupType(GroupType): """groups that are default (typically 'default')""" - pass class LeadershipGroupType(GroupType): """allowed normal group (OptionDescription) names *leadership* means : groups that have the 'leadership' attribute set """ - pass class RootGroupType(GroupType): """root means this is the root optiondescription of whole config """ - pass def addgroup(self, name): + """add a new group type + """ setattr(groups, name, groups.GroupType(name)) @@ -329,14 +336,13 @@ class OwnerModule(_NameSpace): owners are living in `Config._value_owners` """ + # pylint: disable=too-few-public-methods class Owner(str): """allowed owner names """ - pass class DefaultOwner(Owner): """groups that are default (typically 'default')""" - pass def addowner(self, name): """ @@ -348,41 +354,39 @@ class OwnerModule(_NameSpace): # ____________________________________________________________ # populate groups groups = GroupModule() -"""groups.default - default group set when creating a new optiondescription""" -groups.default = groups.DefaultGroupType('default') -"""groups.leadership - leadership group is a special optiondescription, all suboptions should - 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.default: default group set when creating a new optiondescription +groups.default = groups.DefaultGroupType('default') # pylint: disable=attribute-defined-outside-init -""" groups.root - this group is the root optiondescription of whole config""" -groups.root = groups.RootGroupType('root') +# groups.leadership: leadership group is a special optiondescription, all suboptions should +# 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') # 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 owners = OwnerModule() -"""default - is the config owner after init time""" -owners.default = owners.DefaultOwner('default') -"""user - is the generic is the generic owner""" -owners.user = owners.Owner('user') -"""forced - special owner when value is forced""" -owners.forced = owners.Owner('forced') + +# default: is the config owner after init time +owners.default = owners.DefaultOwner('default') # pylint: disable=attribute-defined-outside-init + +# user: is the generic is the generic owner +owners.addowner('user') + +#forced: special owner when value is 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" __slots__ = ('_properties', '_permissives', @@ -417,33 +421,33 @@ class Settings(object): def get_context_properties(self, cache, ): - is_cached, props, validated = cache.getcache(None, - None, - None, - {}, - {}, - 'context_props', - ) + """get context properties + """ + is_cached, props, _ = cache.getcache(None, + 'context_props', + expiration=False, + ) 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, - None, props, - {}, - props, - True) + type_='properties', + ) return props - def _getproperties(self, - path, - index, - default_properties, - ): - if path not in self._properties: - ret = frozenset(default_properties) - else: - ret = self._properties[path].get(index, frozenset(default_properties)) - return ret + def get_stored_properties(self, + path: Union[None, str], + index: Union[None, int], + default_properties: Set[str], + ) -> Set[str]: + """Get the properties modified by user for a path or index + """ + if path not in self._properties or index not in self._properties[path]: + return frozenset(default_properties) + return self._properties[path][index] def getproperties(self, option_bag, @@ -451,21 +455,15 @@ class Settings(object): uncalculated=False, help_property=False, ): + """get properties """ - """ + # pylint: disable=too-many-branches option = option_bag.option - config_bag = option_bag.config_bag if option.impl_is_symlinkoption(): option = option.impl_getopt() - path = option.impl_getpath() - index = option_bag.index if apply_requires and not uncalculated and not help_property: - cache = config_bag.context.properties_cache - is_cached, props, validated = cache.getcache(path, - config_bag.expiration_time, - index, - config_bag.properties, - {}, + cache = option_bag.config_bag.context.properties_cache + is_cached, props, validated = cache.getcache(option_bag, # pylint: disable=unused-variable 'self_props', ) else: @@ -473,13 +471,16 @@ class Settings(object): if not is_cached: props = set() # if index, get option's properties (without index) too - p_props = self._getproperties(path, - None, - option.impl_getproperties(), - ) - if index is not None: + p_props = self.get_stored_properties(option_bag.path, + None, + option.impl_getproperties(), + ) + if option_bag.index is not None: 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: if uncalculated or isinstance(prop, str): @@ -502,43 +503,29 @@ class Settings(object): new_prop = (new_prop, new_prop) if new_prop is None: 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)): - raise ValueError(_('invalid property type {} for {} with {} function').format(type(new_prop), - option_bag.option.impl_getname(), - prop.function.__name__)) + raise ValueError(_('invalid property type {type(new_prop)} for ' + '{option_bag.option.impl_getname()} with ' + '{prop.function.__name__} function')) if not option.impl_is_optiondescription() and \ option.impl_is_leader() and \ 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 -= self.getpermissives(option_bag) - if not uncalculated and apply_requires and not config_bag.is_unrestraint and not help_property: - cache.setcache(path, - index, + if not uncalculated and apply_requires and \ + not option_bag.config_bag.is_unrestraint and \ + not help_property: + cache.setcache(option_bag, props, - props, - config_bag.properties, - True) + type_='properties', + ) 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): + """get context permissives + """ return self.getpermissives(None) def _getpermissives(self, @@ -554,6 +541,8 @@ class Settings(object): def getpermissives(self, option_bag, ): + """get permissive + """ if option_bag is None: path = None index = None @@ -576,6 +565,8 @@ class Settings(object): #____________________________________________________________ # set methods def set_context_properties(self, properties, context): + """set context properties + """ self._properties.setdefault(None, {})[None] = properties context.reset_cache(None) @@ -587,21 +578,16 @@ class Settings(object): (never save properties if same has option properties) """ 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(): not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES if not_allowed_properties: - if len(not_allowed_properties) == 1: - raise LeadershipError(_('leader cannot have "{}" property').format(list(not_allowed_properties)[0])) - else: - raise LeadershipError(_('leader cannot have {} properties').format(display_list(list(not_allowed_properties), add_quote=True))) - if ('force_default_on_freeze' in properties or 'force_metaconfig_on_freeze' in properties) and \ - 'frozen' not in properties: - raise LeadershipError(_('a leader ({0}) cannot have ' - '"force_default_on_freeze" or "force_metaconfig_on_freeze" property without "frozen"' - '').format(opt.impl_get_display_name())) + raise LeadershipError(_('leader cannot have "{list(not_allowed_properties)}" ' + 'property')) + if ('force_default_on_freeze' in properties or \ + 'force_metaconfig_on_freeze' in properties) and 'frozen' not in properties: + raise LeadershipError(_('a leader ({opt.impl_get_display_name()}) cannot have ' + '"force_default_on_freeze" or ' + '"force_metaconfig_on_freeze" property without "frozen"')) self._properties.setdefault(option_bag.path, {})[option_bag.index] = properties # values too because of follower values could have a PropertiesOptionError has value option_bag.config_bag.context.reset_cache(option_bag) @@ -610,6 +596,8 @@ class Settings(object): def set_context_permissives(self, permissives, ): + """set context permissive + """ self.setpermissives(None, permissives, ) @@ -630,10 +618,6 @@ class Settings(object): if not isinstance(permissives, frozenset): raise TypeError(_('permissive must be a frozenset')) 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 index = option_bag.index else: @@ -670,25 +654,29 @@ class Settings(object): def reset(self, bag: Union[OptionBag, ConfigBag], ): + """reset property + """ path, index, config_bag, option_bag = \ self._get_path_index_config_option(bag, "can't reset properties to " "the symlinkoption \"{}\"", ) 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) def reset_permissives(self, bag: Union[OptionBag, ConfigBag], ): + """reset permission + """ path, index, config_bag, option_bag = \ self._get_path_index_config_option(bag, "can't reset permissives to " "the symlinkoption \"{}\"", ) 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) #____________________________________________________________ @@ -698,6 +686,8 @@ class Settings(object): apply_requires=True, uncalculated=False, ): + """raise if needed + """ if not uncalculated and apply_requires: option_properties = option_bag.properties else: @@ -707,12 +697,14 @@ class Settings(object): ) return self._calc_raises_properties(option_bag.config_bag.properties, option_bag.config_bag.permissives, - option_properties) + option_properties, + ) def _calc_raises_properties(self, context_properties, context_permissives, - option_properties): + option_properties, + ): raises_properties = context_properties - SPECIAL_PROPERTIES # remove global permissive properties if raises_properties and 'permissive' in raises_properties: @@ -725,6 +717,8 @@ class Settings(object): option_bag, need_help=True, ): + """check properties + """ config_properties = option_bag.config_bag.properties if not config_properties or config_properties == frozenset(['cache']): # if no global property @@ -747,49 +741,52 @@ class Settings(object): raise PropertiesOptionError(option_bag, properties, self, - help_properties=calc_properties) + help_properties=calc_properties, + ) def validate_mandatory(self, value, option_bag, ): - if 'mandatory' in option_bag.config_bag.properties: - values = option_bag.config_bag.context.get_values() - if option_bag.option.impl_is_follower(): - force_allow_empty_list = True - else: - force_allow_empty_list = False - if not ('permissive' in option_bag.config_bag.properties and - 'mandatory' in option_bag.config_bag.permissives) and \ - 'mandatory' in option_bag.properties and values.isempty(option_bag.option, - value, - force_allow_empty_list=force_allow_empty_list, - index=option_bag.index, - ): - raise PropertiesOptionError(option_bag, - ['mandatory'], - self, - ) - if 'empty' in option_bag.properties and values.isempty(option_bag.option, - value, - force_allow_empty_list=True, - index=option_bag.index, - ): - raise PropertiesOptionError(option_bag, - ['empty'], - self, - ) + """verify if option is mandatory without value + """ + if 'mandatory' not in option_bag.config_bag.properties: + return + values = option_bag.config_bag.context.get_values() + if not ('permissive' in option_bag.config_bag.properties and + 'mandatory' in option_bag.config_bag.permissives) and \ + 'mandatory' in option_bag.properties and values.isempty(option_bag, + value, + False, + ): + raise PropertiesOptionError(option_bag, + ['mandatory'], + self, + ) + if 'empty' in option_bag.properties and values.isempty(option_bag, + value, + True, + ): + raise PropertiesOptionError(option_bag, + ['empty'], + self, + ) def validate_frozen(self, - option_bag): + option_bag, + ): + """verify if option is frozen + """ if option_bag.config_bag.properties and \ ('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 'frozen' in option_bag.config_bag.permissives): raise PropertiesOptionError(option_bag, ['frozen'], - self) + self, + ) return False #____________________________________________________________ # read only/read write @@ -799,7 +796,10 @@ class Settings(object): append, config_bag, ): - props = self._properties.get(None, {}).get(None, self.default_properties) + props = self.get_stored_properties(None, + None, + self.default_properties, + ) modified = False if remove & props: props = props - remove diff --git a/tiramisu/todict.py b/tiramisu/todict.py index 1dcebf3..c484f4a 100644 --- a/tiramisu/todict.py +++ b/tiramisu/todict.py @@ -207,7 +207,7 @@ class Requires(object): else: act = 'hide' inv_act = 'show' - if option.get_type() == 'choice': + if isinstance(option, ChoiceOption): require_option = self.tiramisu_web.config.unrestraint.option(option_path) values = self.tiramisu_web.get_enum(require_option, require_option.option.ismulti(), diff --git a/tiramisu/value.py b/tiramisu/value.py index 9a0744e..b0009f2 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -15,39 +15,37 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -import weakref -from typing import Optional, Any, Callable -from .error import ConfigError, PropertiesOptionError -from .setting import owners, undefined, forbidden_owners, OptionBag, ConfigBag -from .autolib import Calculation, carry_out_calculation, Params +from typing import Union, Optional, List, Any +from .error import ConfigError +from .setting import owners, undefined, forbidden_owners, OptionBag +from .autolib import Calculation from .i18n import _ class Values: - """The `Config`'s root is indeed in charge of the `Option()`'s values, - but the values are physicaly located here, in `Values`, wich is also - responsible of a caching utility. + """This class manage value (default value, stored value or calculated value + It's also responsible of a caching utility. """ + # pylint: disable=too-many-public-methods __slots__ = ('_values', '_informations', '__weakref__', ) def __init__(self, - default_values=None, - ): + default_values: Union[None, dict]=None, + ) -> None: """ 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 = {} # set default owner if not default_values: - self._values = {None: {None: [None, owners.user]}} - else: - self._values = default_values + default_values = {None: {None: [None, owners.user]}} + self._values = default_values #______________________________________________________________________ # get value @@ -61,17 +59,13 @@ class Values: """ # try to retrive value in cache setting_properties = option_bag.config_bag.properties - cache = option_bag.config_bag.context._impl_values_cache - is_cached, value, validated = cache.getcache(option_bag.path, - option_bag.config_bag.expiration_time, - option_bag.index, - setting_properties, - option_bag.properties, - 'value', + cache = option_bag.config_bag.context.get_values_cache() + is_cached, value, validated = cache.getcache(option_bag, + 'values', ) # no cached value so get value if not is_cached: - value = self.getvalue(option_bag) + value = self.get_value(option_bag) # validates and warns value if not validated: validate = option_bag.option.impl_validate(value, @@ -85,12 +79,9 @@ class Values: ) # set value to cache if not is_cached: - cache.setcache(option_bag.path, - option_bag.index, + cache.setcache(option_bag, value, - option_bag.properties, - setting_properties, - validate, + validated=validate, ) if isinstance(value, list): # return a copy, so value cannot be modified @@ -98,81 +89,28 @@ class Values: # and return it return value - def force_to_metaconfig(self, option_bag): - # force_metaconfig_on_freeze in config => to metaconfig - # force_metaconfig_on_freeze in option + config is kernelconfig => to metaconfig - settings = option_bag.config_bag.context.get_settings() - 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` + def get_value(self, + option_bag: OptionBag, + ) -> Any: + """actually retrieves the stored value or the default value (value modified by user) :returns: value """ - # get owner and value from store - # index allowed only for follower - index = option_bag.index - 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]) + default_value = [undefined, owners.default] + value, owner = self._values.get(option_bag.path, {}).get(option_bag.index, default_value) if owner == owners.default or \ ('frozen' in option_bag.properties and \ - ('force_default_on_freeze' in option_bag.properties or self.force_to_metaconfig(option_bag))): - value = self.getdefaultvalue(option_bag) - else: - value = self.calc_value(option_bag, value) + ('force_default_on_freeze' in option_bag.properties or \ + self.check_force_to_metaconfig(option_bag))): + # the value is a default value + # get it + value = self.get_default_value(option_bag) return value - def calc_value(self, - option_bag, - value, - 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, - ): + def get_default_value(self, + option_bag: OptionBag, + ) -> Any: """get default value: - get parents config value or - get calculated value or @@ -183,45 +121,84 @@ class Values: # retrieved value from parent config return moption_bag.config_bag.context.get_values().get_cached_value(moption_bag) - # now try to get default value: - value = self.calc_value(option_bag, - option_bag.option.impl_getdefault(), - ) - if option_bag.index is not None and isinstance(value, (list, tuple)): - if value and option_bag.option.impl_is_submulti(): - # first index is a list, assume other data are list too - if isinstance(value[0], list): - # 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()) - 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()) + # now try to get calculated value: + value = self.get_calculated_value(option_bag, + option_bag.option.impl_getdefault(), + ) + if option_bag.index is not None and isinstance(value, (list, tuple)) \ + and (not option_bag.option.impl_is_submulti() or \ + not value or isinstance(value[0], list)): + # if index (so slave), 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: + 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.get_calculated_value(option_bag, + option_bag.option.impl_getdefault_multi(), + ) return value - def calculate_reset_cache(self, - option_bag, - value): - if not 'expire' in option_bag.properties: - return - cache = option_bag.config_bag.context._impl_values_cache - is_cache, cache_value, validated = cache.getcache(option_bag.path, - None, - option_bag.index, - option_bag.config_bag.properties, - option_bag.properties, - 'value') + def get_calculated_value(self, + option_bag, + value, + reset_cache=True, + ) -> Any: + """value could be a calculation, in this case do calculation + """ + 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()}": ' + 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: # calculation return same value as previous value, # so do not invalidate cache @@ -232,30 +209,47 @@ class Values: self._set_force_value_suffix(option_bag) def isempty(self, - opt, - value, - force_allow_empty_list=False, - index=None): - "convenience method to know if an option is empty" - empty = opt._empty - if index in [None, undefined] and opt.impl_is_multi(): - isempty = value is None or (isinstance(value, list) and not force_allow_empty_list and value == []) or \ - (isinstance(value, list) and None in value) or empty in value + option_bag: OptionBag, + value: Any, + force_allow_empty_list: bool, + ) -> bool: + """convenience method to know if an option is empty + """ + if option_bag.index is None and option_bag.option.impl_is_submulti(): + # index is not set + 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: - isempty = value is None or value == empty or (opt.impl_is_submulti() and value == []) + isempty = value is None or value == '' 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 def set_value(self, - option_bag, - value, - ): - context = option_bag.config_bag.context + option_bag: OptionBag, + value: Any, + ) -> None: + """set value to option + """ owner = self.get_context_owner() if 'validator' in option_bag.config_bag.properties: self.setvalue_validation(value, - option_bag) + option_bag, + ) if isinstance(value, list): # copy @@ -265,33 +259,35 @@ class Values: owner, ) 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: - cache = option_bag.config_bag.context._impl_values_cache - cache.setcache(option_bag.path, - option_bag.index, + cache = option_bag.config_bag.context.get_values_cache() + cache.setcache(option_bag, value, - option_bag.properties, - setting_properties, - validator) + validated=validator, + ) 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, - value, - option_bag, - owners.forced, - ) + leader = option_bag.option.impl_get_leadership() + leader.follower_force_store_value(value, + option_bag.config_bag, + owners.forced, + ) def setvalue_validation(self, value, option_bag, ): + """validate value before set value + """ settings = option_bag.config_bag.context.get_settings() # First validate properties with this value opt = option_bag.option settings.validate_frozen(option_bag) - val = self.calc_value(option_bag, - value, - False,) + val = self.get_calculated_value(option_bag, + value, + False, + ) settings.validate_mandatory(val, option_bag, ) @@ -320,25 +316,25 @@ class Values: ) 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, path, index, value, owner, ): + """set a value + """ self._values.setdefault(path, {})[index] = [value, owner] - def _set_force_value_suffix(self, - option_bag: OptionBag, - ) -> None: + def _set_force_value_suffix(self, option_bag: OptionBag) -> None: + """ force store value for an option for suffixes + """ + # pylint: disable=too-many-locals if 'force_store_value' not in option_bag.config_bag.properties: 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() force_store_options = [] for coption in option.get_children_recursively(None, @@ -350,10 +346,10 @@ class Values: if not force_store_options: continue rootpath = option.impl_getpath() - settings = option_bag.config_bag.context.get_settings() for suffix in option.get_suffixes(option_bag.config_bag): 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) subpath = '.'.join([subp + path_suffix for subp in subpaths]) doption = coption.to_dynoption(subpath, @@ -367,7 +363,7 @@ class Values: option_bag.config_bag, properties=frozenset(), ) - indexes = range(len(self.getvalue(loption_bag))) + indexes = range(len(self.get_value(loption_bag))) else: indexes = [None] for index in indexes: @@ -375,7 +371,8 @@ class Values: index, 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, option_bag: OptionBag, @@ -398,8 +395,9 @@ class Values: # remove force_metaconfig_on_freeze only if option in metaconfig # hasn't force_metaconfig_on_freeze properties ori_properties = doption_bag.properties - doption_bag.properties = doption_bag.config_bag.context.get_settings().getproperties(doption_bag) - if not self.force_to_metaconfig(doption_bag): + settings = doption_bag.config_bag.context.get_settings() + doption_bag.properties = settings.getproperties(doption_bag) + if not self.check_force_to_metaconfig(doption_bag): doption_bag.properties = ori_properties - {'force_metaconfig_on_freeze'} else: doption_bag.properties = ori_properties @@ -416,11 +414,15 @@ class Values: # owner def is_default_owner(self, - option_bag, - validate_meta=True): + option_bag: OptionBag, + validate_meta: bool=True, + ) -> bool: + """is default owner for an option + """ return self.getowner(option_bag, validate_meta=validate_meta, - only_default=True) == owners.default + only_default=True, + ) == owners.default def hasvalue(self, path, @@ -432,7 +434,7 @@ class Values: has_path = path in self._values if index is None: return has_path - elif has_path: + if has_path: return index in self._values[path] return False @@ -468,14 +470,18 @@ class Values: else: owner = owners.default else: - owner = self._values.get(option_bag.path, {}).get(option_bag.index, [undefined, owners.default])[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): + owner = self._values.get(option_bag.path, {}).get(option_bag.index, + [undefined, owners.default], + )[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) if moption_bag is not None: - owner = moption_bag.config_bag.context.get_values().getowner(moption_bag, - only_default=only_default, - ) + values = moption_bag.config_bag.context.get_values() + owner = values.getowner(moption_bag, + only_default=only_default, + ) elif 'force_metaconfig_on_freeze' in option_bag.properties: return owners.default return owner @@ -487,32 +493,28 @@ class Values: """ 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 """ - 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: raise ValueError(_('set owner "{0}" is forbidden').format(str(owner))) if not self.hasvalue(option_bag.path, option_bag.index): - raise ConfigError(_('no value for {0} cannot change owner to {1}' - '').format(option_bag.path, owner)) + raise ConfigError(_(f'no value for {option_bag.path} cannot change owner to {owner}')) option_bag.config_bag.context.get_settings().validate_frozen(option_bag) self._values[option_bag.path][option_bag.index][1] = owner #______________________________________________________________________ # reset - def reset(self, - option_bag): + def reset(self, option_bag: OptionBag) -> None: + """reset value for an option + """ context = option_bag.config_bag.context hasvalue = self.hasvalue(option_bag.path) setting_properties = 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.remove_validation() config_bag.context = fake_context @@ -521,16 +523,17 @@ class Values: fake_value = fake_context.get_values() fake_value.reset(soption_bag) 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, - soption_bag) + soption_bag, + ) opt = option_bag.option if opt.impl_is_leader(): - opt.impl_get_leadership().reset(self, - option_bag) + opt.impl_get_leadership().reset(option_bag.config_bag) if hasvalue: - if 'force_store_value' in option_bag.config_bag.properties and 'force_store_value' in option_bag.properties: - value = self.getdefaultvalue(option_bag) + if 'force_store_value' in option_bag.config_bag.properties and \ + 'force_store_value' in option_bag.properties: + value = self.get_default_value(option_bag) self._setvalue(option_bag, value, @@ -544,72 +547,89 @@ class Values: context.reset_cache(option_bag) if 'force_store_value' in setting_properties and option_bag.option.impl_is_leader(): if value is None: - value = self.getdefaultvalue(option_bag) - option_bag.option.impl_get_leadership().follower_force_store_value(self, - value, - option_bag, - owners.forced) + value = self.get_default_value(option_bag) + leader = option_bag.option.impl_get_leadership() + leader.follower_force_store_value(value, + option_bag.config_bag, + 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, {}) if values: return max(values) + 1 return 0 def reset_follower(self, - option_bag): - if self.hasvalue(option_bag.path, - index=option_bag.index): - context = option_bag.config_bag.context - setting_properties = option_bag.config_bag.properties - if 'validator' in setting_properties: - fake_context = context._gen_fake_values() - fake_value = fake_context.get_values() - config_bag = option_bag.config_bag.copy() - config_bag.remove_validation() - config_bag.context = fake_context - soption_bag = option_bag.copy() - soption_bag.config_bag = config_bag - fake_value.reset_follower(soption_bag) - value = fake_value.getdefaultvalue(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.getdefaultvalue(option_bag) + option_bag: OptionBag, + ) -> None: + """reset value for a follower + """ + if not self.hasvalue(option_bag.path, + index=option_bag.index, + ): + return + context = option_bag.config_bag.context + setting_properties = option_bag.config_bag.properties + if 'validator' in setting_properties: + fake_context = context.gen_fake_values() + fake_value = fake_context.get_values() + config_bag = option_bag.config_bag.copy() + config_bag.remove_validation() + config_bag.context = fake_context + soption_bag = option_bag.copy() + soption_bag.config_bag = config_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, - value, - owners.forced, - ) - else: - self.resetvalue_index(option_bag.path, - option_bag.index, - ) - context.reset_cache(option_bag) + self._setvalue(option_bag, + value, + owners.forced, + ) + else: + self.resetvalue_index(option_bag) + context.reset_cache(option_bag) - def resetvalue_index(self, - path, - index, - ): - if path in self._values and index in self._values[path]: - del self._values[path][index] + def resetvalue_index(self, option_bag: OptionBag) -> None: + """reset a value for a follower at an index + """ + if option_bag.path in self._values and option_bag.index in self._values[option_bag.path]: + del self._values[option_bag.path][option_bag.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, option_bag: OptionBag, leadership_option_bag: OptionBag, index: int, - ): + ) -> None: + """reset leadershop from an index + """ current_value = self.get_cached_value(option_bag) length = len(current_value) if index >= length: - raise IndexError(_('index {} is greater than the length {} ' - 'for option "{}"').format(index, - length, - option_bag.option.impl_get_display_name())) + raise IndexError(_('index {index} is greater than the length {length} ' + 'for option "{option_bag.option.impl_get_display_name()}"')) current_value.pop(index) - leadership_option_bag.option.pop(self, - index, - option_bag, + leadership_option_bag.option.pop(index, + option_bag.config_bag, ) self.set_value(option_bag, current_value, @@ -619,7 +639,6 @@ class Values: # information def set_information(self, - config_bag, option_bag, key, value, @@ -634,55 +653,62 @@ class Values: else: path = option_bag.path self._informations.setdefault(path, {})[key] = value - if path is not None: - for option in option_bag.option.get_dependencies_information(itself=True): - config_bag.context.reset_cache(option_bag) + if path is None: + return + if key in option_bag.option.get_dependencies_information(itself=True): + option_bag.config_bag.context.reset_cache(option_bag) def get_information(self, - config_bag, option_bag, - key, + name, default, ): """retrieves one information's item - :param key: the item string (ex: "help") + :param name: the item string (ex: "help") """ if option_bag is None: path = None else: path = option_bag.path try: - return self._informations[path][key] + return self._informations[path][name] except KeyError as err: 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: 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, - key, - raises=True, - path=None, + key: Any, + raises: bool=True, + path: str=None, ): + """delete information for a specified key + """ if path in self._informations and key in self._informations[path]: del self._informations[path][key] elif raises: raise ValueError(_(f"information's item not found \"{key}\"")) 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()) #____________________________________________________________ # default owner methods - def set_context_owner(self, owner): - ":param owner: sets the default value for owner at the Config level" + def set_context_owner(self, owner: str) -> None: + """set the context owner + """ if owner in forbidden_owners: raise ValueError(_('set owner "{0}" is forbidden').format(str(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]