diff --git a/settngs.py b/settngs.py index c23dff3..445595c 100644 --- a/settngs.py +++ b/settngs.py @@ -197,7 +197,7 @@ class Setting: if dest: dest_name = dest if not dest_name.isidentifier(): - raise Exception('Cannot use {dest_name} in a namespace') + raise Exception(f'Cannot use {dest_name} in a namespace') internal_name = f'{prefix}_{dest_name}'.lstrip('_') return internal_name, dest_name, flag @@ -304,10 +304,15 @@ def normalize_config( if (setting.cmdline and cmdline) or (setting.file and file): # Ensures the option exists with the default if not already set value, default = get_option(options, setting) - if not default or default and defaults: + if not default or (default and defaults): + # User has set a custom value or has requested the default value group_options[setting_name] = value elif setting_name in group_options: + # defaults have been requested to be removed del group_options[setting_name] + elif setting_name in group_options: + # Setting type (file or cmdline) has not been requested and should be removed for persistent groups + del group_options[setting_name] normalized[group_name] = group_options return Config(normalized, definitions) @@ -374,7 +379,7 @@ def get_namespace(config: Config[T], defaults: bool = True, persistent: bool = T """ if isinstance(config.values, Namespace): - options, definitions = normalize_config(config) + options, definitions = normalize_config(config, defaults=defaults, persistent=persistent) else: options, definitions = config namespace = Namespace() @@ -459,7 +464,7 @@ def parse_cmdline( description: str, epilog: str, args: list[str] | None = None, - config: Namespace | Config[T] | None = None, + config: ns[T] = None, ) -> Config[Values]: """ Creates an `argparse.ArgumentParser` from cmdline settings in `self.definitions`. diff --git a/tests/settngs_test.py b/tests/settngs_test.py index 81b7ae2..ca292ed 100644 --- a/tests/settngs_test.py +++ b/tests/settngs_test.py @@ -59,7 +59,7 @@ def test_get_defaults(settngs_manager): assert defaults['']['test'] == 'hello' -def test_get_namespace(settngs_manager): +def test_get_defaults_namespace(settngs_manager): settngs_manager.add_setting('--test', default='hello') defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults()) assert defaults.test == 'hello' @@ -67,8 +67,8 @@ def test_get_namespace(settngs_manager): def test_get_namespace_with_namespace(settngs_manager): settngs_manager.add_setting('--test', default='hello') - defaults, _ = settngs_manager.get_namespace(argparse.Namespace(test='hello')) - assert defaults.test == 'hello' + defaults, _ = settngs_manager.get_namespace(argparse.Namespace(test='success')) + assert defaults.test == 'success' def test_get_defaults_group(settngs_manager): @@ -97,6 +97,20 @@ def test_cmdline_only(settngs_manager): assert 'test2' in file_normalized['tst2'] +def test_cmdline_only_persistent_group(settngs_manager): + settngs_manager.add_persistent_group('tst', lambda parser: parser.add_setting('--test', default='hello', file=False)) + settngs_manager.add_group('tst2', lambda parser: parser.add_setting('--test2', default='hello', cmdline=False)) + + file_normalized, _ = settngs_manager.normalize_config(settngs_manager.defaults(), file=True) + cmdline_normalized, _ = settngs_manager.normalize_config(settngs_manager.defaults(), cmdline=True) + + assert 'test' in cmdline_normalized['tst'] + assert 'test2' not in cmdline_normalized['tst2'] + + assert 'test' not in file_normalized['tst'] + assert 'test2' in file_normalized['tst2'] + + def test_normalize(settngs_manager): settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world')) @@ -118,11 +132,13 @@ def test_normalize(settngs_manager): assert 'test' in normalized['tst'] assert normalized['tst']['test'] == 'hello' assert normalized['persistent']['hello'] == 'success' + assert normalized['persistent']['world'] == 'world' assert not hasattr(normalized_namespace, 'test') assert hasattr(normalized_namespace, 'tst_test') assert normalized_namespace.tst_test == 'hello' assert normalized_namespace.persistent_hello == 'success' + assert normalized_namespace.persistent_world == 'world' def test_clean_config(settngs_manager): @@ -151,15 +167,25 @@ def test_parse_cmdline(settngs_manager): assert normalized['tst']['test'] == 'success' -def test_parse_cmdline_with_namespace(settngs_manager): +namespaces = ( + lambda definitions: settngs.Config({'tst': {'test': 'fail', 'test2': 'success'}}, definitions), + lambda definitions: settngs.Config(argparse.Namespace(tst_test='fail', tst_test2='success'), definitions), + lambda definitions: argparse.Namespace(tst_test='fail', tst_test2='success'), +) + + +@pytest.mark.parametrize('ns', namespaces) +def test_parse_cmdline_with_namespace(settngs_manager, ns): settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello', cmdline=True)) + settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test2', default='fail', cmdline=True)) normalized, _ = settngs_manager.parse_cmdline( - ['--test', 'success'], namespace=settngs.Config({'tst': {'test': 'fail'}}, settngs_manager.definitions), + ['--test', 'success'], namespace=ns(settngs_manager.definitions), ) assert 'test' in normalized['tst'] assert normalized['tst']['test'] == 'success' + assert normalized['tst']['test2'] == 'success' def test_parse_file(settngs_manager, tmp_path):