Persist unknown groups

This commit is contained in:
Timmy Welch 2023-11-17 23:45:37 -08:00
parent ccacca1b32
commit 0c49b9309d
5 changed files with 139 additions and 66 deletions

View File

@ -29,14 +29,14 @@ $ python -m settngs
Hello lordwelch Hello lordwelch
$ python -m settngs -v $ python -m settngs -v
Hello lordwelch Hello lordwelch
merged_namespace.values.example_verbose=True merged_namespace.values.Example_Group__verbose=True
$ python -m settngs -v -s $ python -m settngs -v -s
Hello lordwelch Hello lordwelch
Successfully saved settings to settings.json Successfully saved settings to settings.json
merged_namespace.values.example_verbose=True merged_namespace.values.Example_Group__verbose=True
$ python -m settngs $ python -m settngs
Hello lordwelch Hello lordwelch
merged_namespace.values.example_verbose=True merged_namespace.values.Example_Group__verbose=True
$ cat >settings.json << EOF $ cat >settings.json << EOF
{ {
"example": { "example": {

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import copy
import json import json
import logging import logging
import pathlib import pathlib
@ -247,7 +248,7 @@ class Setting:
if not dest_name.isidentifier(): if not dest_name.isidentifier():
raise Exception(f'Cannot use {dest_name} in a namespace') raise Exception(f'Cannot use {dest_name} in a namespace')
internal_name = f'{prefix}_{dest_name}'.lstrip('_') internal_name = f'{prefix}__{dest_name}'.lstrip('_')
return internal_name, dest_name, flag return internal_name, dest_name, flag
def filter_argparse_kwargs(self) -> dict[str, Any]: def filter_argparse_kwargs(self) -> dict[str, Any]:
@ -376,11 +377,34 @@ def get_options(config: Config[T], group: str) -> dict[str, Any]:
if name in internal_names: if name in internal_names:
values[internal_names[name].dest] = value values[internal_names[name].dest] = value
else: else:
values[removeprefix(name, f'{group}_')] = value values[removeprefix(name, f'{group}').lstrip('_')] = value
return values return values
def get_groups(values: Values | Namespace | TypedNS) -> list[str]:
if isinstance(values, dict):
return [x[0] for x in values.items() if isinstance(x[1], dict)]
if isinstance(values, Namespace):
groups = set()
for name in values.__dict__:
if '__' in name:
group, _, _ = name.partition('__')
groups.add(group.replace('_', ' '))
else:
groups.add('')
return list(groups)
return []
def _get_internal_definitions(config: Config[T], persistent: bool) -> Definitions:
definitions = copy.deepcopy(dict(config.definitions))
if persistent:
for group_name in get_groups(config.values):
if group_name not in definitions:
definitions[group_name] = Group(True, {})
return defaultdict(lambda: Group(False, {}), definitions)
def normalize_config( def normalize_config(
config: Config[T], config: Config[T],
file: bool = False, file: bool = False,
@ -398,18 +422,19 @@ def normalize_config(
file: Include file options file: Include file options
cmdline: Include cmdline options cmdline: Include cmdline options
default: Include default values in the returned Config object default: Include default values in the returned Config object
persistent: Include unknown keys in persistent groups persistent: Include unknown keys in persistent groups and unknown groups
""" """
if not file and not cmdline: if not file and not cmdline:
raise ValueError('Invalid parameters: you must set either file or cmdline to True') raise ValueError('Invalid parameters: you must set either file or cmdline to True')
normalized: Values = {} normalized: Values = {}
options, definitions = config options = config.values
definitions = _get_internal_definitions(config=config, persistent=persistent)
for group_name, group in definitions.items(): for group_name, group in definitions.items():
group_options = {} group_options = {}
if group.persistent and persistent: if group.persistent and persistent:
group_options = get_options(config, group_name) group_options = get_options(Config(options, definitions), group_name)
for setting_name, setting in group.v.items(): for setting_name, setting in group.v.items():
if (setting.cmdline and cmdline) or (setting.file and file): if (setting.cmdline and cmdline) or (setting.file and file):
# Ensures the option exists with the default if not already set # Ensures the option exists with the default if not already set
@ -424,7 +449,8 @@ def normalize_config(
# Setting type (file or cmdline) has not been requested and should be removed for persistent groups # Setting type (file or cmdline) has not been requested and should be removed for persistent groups
del group_options[setting_name] del group_options[setting_name]
normalized[group_name] = group_options normalized[group_name] = group_options
return Config(normalized, definitions)
return Config(normalized, config.definitions)
def parse_file(definitions: Definitions, filename: pathlib.Path) -> tuple[Config[Values], bool]: def parse_file(definitions: Definitions, filename: pathlib.Path) -> tuple[Config[Values], bool]:
@ -467,7 +493,7 @@ def clean_config(
file: Include file options file: Include file options
cmdline: Include cmdline options cmdline: Include cmdline options
default: Include default values in the returned Config object default: Include default values in the returned Config object
persistent: Include unknown keys in persistent groups persistent: Include unknown keys in persistent groups and unknown groups
""" """
cleaned, _ = normalize_config(config, file=file, cmdline=cmdline, default=default, persistent=persistent) cleaned, _ = normalize_config(config, file=file, cmdline=cmdline, default=default, persistent=persistent)
@ -493,24 +519,22 @@ def get_namespace(
file: Include file options file: Include file options
cmdline: Include cmdline options cmdline: Include cmdline options
default: Include default values in the returned Config object default: Include default values in the returned Config object
persistent: Include unknown keys in persistent groups persistent: Include unknown keys in persistent groups and unknown groups
""" """
if not file and not cmdline: if not file and not cmdline:
raise ValueError('Invalid parameters: you must set either file or cmdline to True') raise ValueError('Invalid parameters: you must set either file or cmdline to True')
options: Values options: Values
definitions: Definitions definitions = _get_internal_definitions(config=config, persistent=persistent)
if isinstance(config.values, dict): if isinstance(config.values, dict):
options = config.values options = config.values
definitions = config.definitions
else: else:
cfg = normalize_config(config, file=file, cmdline=cmdline, default=default, persistent=persistent) cfg = normalize_config(config, file=file, cmdline=cmdline, default=default, persistent=persistent)
options, definitions = cfg options = cfg.values
namespace = Namespace() namespace = Namespace()
for group_name, group in definitions.items(): for group_name, group in definitions.items():
group_options = get_options(config, group_name) group_options = get_options(Config(options, definitions), group_name)
if group.persistent and persistent: if group.persistent and persistent:
for name, value in group_options.items(): for name, value in group_options.items():
if name in group.v: if name in group.v:
@ -519,7 +543,7 @@ def get_namespace(
internal_name = group.v[name].internal_name internal_name = group.v[name].internal_name
else: else:
setting_file = setting_cmdline = True setting_file = setting_cmdline = True
internal_name, is_default = f'{group_name}_' + sanitize_name(name), None internal_name, is_default = f'{group_name}__' + sanitize_name(name), None
if ((setting_cmdline and cmdline) or (setting_file and file)) and (not is_default or default): if ((setting_cmdline and cmdline) or (setting_file and file)) and (not is_default or default):
setattr(namespace, internal_name, value) setattr(namespace, internal_name, value)
@ -531,7 +555,7 @@ def get_namespace(
if not is_default or default: if not is_default or default:
# User has set a custom value or has requested the default value # User has set a custom value or has requested the default value
setattr(namespace, setting.internal_name, value) setattr(namespace, setting.internal_name, value)
return Config(namespace, definitions) return Config(namespace, config.definitions)
def save_file( def save_file(
@ -660,10 +684,11 @@ class Manager:
self.description = description self.description = description
self.epilog = epilog self.epilog = epilog
self.definitions: Definitions
if isinstance(definitions, Config): if isinstance(definitions, Config):
self.definitions = definitions.definitions self.definitions = defaultdict(lambda: Group(False, {}), dict(definitions.definitions) or {})
else: else:
self.definitions = defaultdict(lambda: Group(False, {}), definitions or {}) self.definitions = defaultdict(lambda: Group(False, {}), dict(definitions or {}))
self.exclusive_group = False self.exclusive_group = False
self.current_group_name = '' self.current_group_name = ''
@ -766,7 +791,7 @@ class Manager:
file: Include file options file: Include file options
cmdline: Include cmdline options cmdline: Include cmdline options
default: Include default values in the returned Config object default: Include default values in the returned Config object
persistent: Include unknown keys in persistent groups persistent: Include unknown keys in persistent groups and unknown groups
""" """
return normalize_config( return normalize_config(
@ -779,7 +804,7 @@ class Manager:
def get_namespace( def get_namespace(
self, self,
config: Values | Config[Values], config: T | Config[T],
file: bool = False, file: bool = False,
cmdline: bool = False, cmdline: bool = False,
default: bool = True, default: bool = True,
@ -795,7 +820,7 @@ class Manager:
file: Include file options file: Include file options
cmdline: Include cmdline options cmdline: Include cmdline options
default: Include default values in the returned Config object default: Include default values in the returned Config object
persistent: Include unknown keys in persistent groups persistent: Include unknown keys in persistent groups and unknown groups
""" """
return get_namespace( return get_namespace(
@ -915,13 +940,13 @@ def _main(args: list[str] | None = None) -> None:
merged_namespace = manager.get_namespace(merged_config, file=True, cmdline=True) merged_namespace = manager.get_namespace(merged_config, file=True, cmdline=True)
print(f'Hello {merged_config.values["Example Group"]["hello"]}') # noqa: T201 print(f'Hello {merged_config.values["Example Group"]["hello"]}') # noqa: T201
if merged_namespace.values.Example_Group_save: if merged_namespace.values.Example_Group__save:
if manager.save_file(merged_config, settings_path): if manager.save_file(merged_config, settings_path):
print(f'Successfully saved settings to {settings_path}') # noqa: T201 print(f'Successfully saved settings to {settings_path}') # noqa: T201
else: # pragma: no cover else: # pragma: no cover
print(f'Failed saving settings to a {settings_path}') # noqa: T201 print(f'Failed saving settings to a {settings_path}') # noqa: T201
if merged_namespace.values.Example_Group_verbose: if merged_namespace.values.Example_Group__verbose:
print(f'{merged_namespace.values.Example_Group_verbose=}') # noqa: T201 print(f'{merged_namespace.values.Example_Group__verbose=}') # noqa: T201
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -90,6 +90,7 @@ fail_under = 95
[mypy] [mypy]
check_untyped_defs = true check_untyped_defs = true
disallow_any_generics = true disallow_any_generics = true
warn_return_any = true
disallow_incomplete_defs = true disallow_incomplete_defs = true
disallow_untyped_defs = true disallow_untyped_defs = true
no_implicit_optional = true no_implicit_optional = true
@ -97,7 +98,9 @@ warn_redundant_casts = true
warn_unused_ignores = true warn_unused_ignores = true
[mypy-testing.*] [mypy-testing.*]
warn_return_any = false
disallow_untyped_defs = false disallow_untyped_defs = false
[mypy-tests.*] [mypy-tests.*]
warn_return_any = false
disallow_untyped_defs = false disallow_untyped_defs = false

View File

@ -24,22 +24,22 @@ example: list[tuple[list[str], str, str]] = [
), ),
( (
['-v'], ['-v'],
'Hello lordwelch\nmerged_namespace.values.Example_Group_verbose=True\n', 'Hello lordwelch\nmerged_namespace.values.Example_Group__verbose=True\n',
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": false\n },\n "persistent": {\n "test": false\n }\n}\n', '{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": false\n },\n "persistent": {\n "test": false\n }\n}\n',
), ),
( (
['-v', '-s'], ['-v', '-s'],
'Hello lordwelch\nSuccessfully saved settings to settings.json\nmerged_namespace.values.Example_Group_verbose=True\n', 'Hello lordwelch\nSuccessfully saved settings to settings.json\nmerged_namespace.values.Example_Group__verbose=True\n',
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\n', '{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\n',
), ),
( (
[], [],
'Hello lordwelch\nmerged_namespace.values.Example_Group_verbose=True\n', 'Hello lordwelch\nmerged_namespace.values.Example_Group__verbose=True\n',
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\n', '{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\n',
), ),
( (
['manual settings.json'], ['manual settings.json'],
'Hello lordwelch\nmerged_namespace.values.Example_Group_verbose=True\n', 'Hello lordwelch\nmerged_namespace.values.Example_Group__verbose=True\n',
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n', '{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n',
), ),
( (
@ -84,7 +84,7 @@ success = [
'flag': True, 'flag': True,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_test_setting', # Should almost always be "{group}_{dest}" 'internal_name': 'tst__test_setting', # Should almost always be "{group}_{dest}"
'metavar': 'TEST_SETTING', # Set manually so argparse doesn't use TST_TEST 'metavar': 'TEST_SETTING', # Set manually so argparse doesn't use TST_TEST
'nargs': None, 'nargs': None,
'required': None, 'required': None,
@ -95,7 +95,7 @@ success = [
'choices': None, 'choices': None,
'const': None, 'const': None,
'default': None, 'default': None,
'dest': 'tst_test_setting', 'dest': 'tst__test_setting',
'help': None, 'help': None,
'metavar': 'TEST_SETTING', 'metavar': 'TEST_SETTING',
'nargs': None, 'nargs': None,
@ -125,7 +125,7 @@ success = [
'flag': True, 'flag': True,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_testing', # Should almost always be "{group}_{dest}" 'internal_name': 'tst__testing', # Should almost always be "{group}_{dest}"
'metavar': 'TESTING', # Set manually so argparse doesn't use TST_TEST 'metavar': 'TESTING', # Set manually so argparse doesn't use TST_TEST
'nargs': None, 'nargs': None,
'required': None, 'required': None,
@ -136,7 +136,7 @@ success = [
'choices': None, 'choices': None,
'const': None, 'const': None,
'default': None, 'default': None,
'dest': 'tst_testing', 'dest': 'tst__testing',
'help': None, 'help': None,
'metavar': 'TESTING', 'metavar': 'TESTING',
'nargs': None, 'nargs': None,
@ -165,7 +165,7 @@ success = [
'flag': True, 'flag': True,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_test', # Should almost always be "{group}_{dest}" 'internal_name': 'tst__test', # Should almost always be "{group}_{dest}"
'metavar': 'TEST', # Set manually so argparse doesn't use TST_TEST 'metavar': 'TEST', # Set manually so argparse doesn't use TST_TEST
'nargs': None, 'nargs': None,
'required': None, 'required': None,
@ -176,7 +176,7 @@ success = [
'choices': None, 'choices': None,
'const': None, 'const': None,
'default': None, 'default': None,
'dest': 'tst_test', 'dest': 'tst__test',
'help': None, 'help': None,
'metavar': 'TEST', 'metavar': 'TEST',
'nargs': None, 'nargs': None,
@ -206,7 +206,7 @@ success = [
'flag': True, 'flag': True,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_test', # Should almost always be "{group}_{dest}" 'internal_name': 'tst__test', # Should almost always be "{group}_{dest}"
'metavar': None, # store_true does not get a metavar 'metavar': None, # store_true does not get a metavar
'nargs': None, 'nargs': None,
'required': None, 'required': None,
@ -217,7 +217,7 @@ success = [
'choices': None, 'choices': None,
'const': None, 'const': None,
'default': None, 'default': None,
'dest': 'tst_test', 'dest': 'tst__test',
'help': None, 'help': None,
'metavar': None, 'metavar': None,
'nargs': None, 'nargs': None,
@ -246,7 +246,7 @@ success = [
'flag': True, 'flag': True,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_test', 'internal_name': 'tst__test',
'metavar': 'TEST', 'metavar': 'TEST',
'nargs': None, 'nargs': None,
'required': None, 'required': None,
@ -257,7 +257,7 @@ success = [
'choices': None, 'choices': None,
'const': None, 'const': None,
'default': None, 'default': None,
'dest': 'tst_test', 'dest': 'tst__test',
'help': None, 'help': None,
'metavar': 'TEST', 'metavar': 'TEST',
'nargs': None, 'nargs': None,
@ -286,12 +286,12 @@ success = [
'flag': False, 'flag': False,
'group': 'tst', 'group': 'tst',
'help': None, 'help': None,
'internal_name': 'tst_test', 'internal_name': 'tst__test',
'metavar': 'TEST', 'metavar': 'TEST',
'nargs': None, 'nargs': None,
'required': None, 'required': None,
'type': None, 'type': None,
'argparse_args': ('tst_test',), 'argparse_args': ('tst__test',),
'argparse_kwargs': { 'argparse_kwargs': {
'action': None, 'action': None,
'choices': None, 'choices': None,

View File

@ -89,7 +89,7 @@ def test_exclusive_group(settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'), exclusive_group=True) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'), exclusive_group=True)
settngs_manager.create_argparser() settngs_manager.create_argparser()
args = settngs_manager.argparser.parse_args(['--test', 'never']) args = settngs_manager.argparser.parse_args(['--test', 'never'])
assert args.tst_test == 'never' assert args.tst__test == 'never'
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test2', default='hello'), exclusive_group=True) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test2', default='hello'), exclusive_group=True)
@ -213,6 +213,29 @@ class TestValues:
assert normalized['persistent']['hello'] == 'success' assert normalized['persistent']['hello'] == 'success'
assert normalized['persistent']['world'] == 'world' assert normalized['persistent']['world'] == 'world'
def test_unknown_group(self):
manager = settngs.Manager()
manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
manager_unknown = settngs.Manager()
manager_unknown.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
# This manager doesn't know about this group
# manager_unknown.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
defaults = manager.defaults()
defaults.values['test'] = 'fail' # type: ignore[assignment] # Not defined in manager, should be removed
defaults.values['persistent']['hello'] = 'success' # Group is not defined in manager_unknown, should stay
normalized, _ = manager_unknown.normalize_config(defaults.values, file=True)
assert 'test' not in normalized
assert 'tst' in normalized
assert 'test' in normalized['tst']
assert normalized['tst']['test'] == 'hello'
assert normalized['persistent']['hello'] == 'success'
assert normalized['persistent']['world'] == 'world'
class TestNamespace: class TestNamespace:
@ -230,12 +253,12 @@ class TestNamespace:
def test_get_defaults_group(self, settngs_manager): def test_get_defaults_group(self, settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True) defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
assert defaults.tst_test == 'hello' assert defaults.tst__test == 'hello'
def test_get_defaults_group_space(self, settngs_manager): def test_get_defaults_group_space(self, settngs_manager):
settngs_manager.add_group('Testing tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_group('Testing tst', lambda parser: parser.add_setting('--test', default='hello'))
defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True) defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
assert defaults.Testing_tst_test == 'hello' assert defaults.Testing_tst__test == 'hello'
def test_cmdline_only(self, settngs_manager): def test_cmdline_only(self, settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello', file=False)) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello', file=False))
@ -244,11 +267,11 @@ class TestNamespace:
file_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), file=True), file=True) file_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), file=True), file=True)
cmdline_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), cmdline=True), cmdline=True) cmdline_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), cmdline=True), cmdline=True)
assert 'tst_test' not in file_normalized.__dict__ assert 'tst__test' not in file_normalized.__dict__
assert 'tst2_test2' in file_normalized.__dict__ assert 'tst2__test2' in file_normalized.__dict__
assert 'tst_test' in cmdline_normalized.__dict__ assert 'tst__test' in cmdline_normalized.__dict__
assert 'tst2_test2' not in cmdline_normalized.__dict__ assert 'tst2__test2' not in cmdline_normalized.__dict__
def test_cmdline_only_persistent_group(self, settngs_manager): def test_cmdline_only_persistent_group(self, settngs_manager):
settngs_manager.add_persistent_group('tst', lambda parser: parser.add_setting('--test', default='hello', file=False)) settngs_manager.add_persistent_group('tst', lambda parser: parser.add_setting('--test', default='hello', file=False))
@ -257,11 +280,11 @@ class TestNamespace:
file_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), file=True), file=True) file_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), file=True), file=True)
cmdline_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), cmdline=True), cmdline=True) cmdline_normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(settngs_manager.defaults(), cmdline=True), cmdline=True)
assert 'tst_test' not in file_normalized.__dict__ assert 'tst__test' not in file_normalized.__dict__
assert 'tst2_test2' in file_normalized.__dict__ assert 'tst2__test2' in file_normalized.__dict__
assert 'tst_test' in cmdline_normalized.__dict__ assert 'tst__test' in cmdline_normalized.__dict__
assert 'tst2_test2' not in cmdline_normalized.__dict__ assert 'tst2__test2' not in cmdline_normalized.__dict__
def test_normalize_defaults(self, settngs_manager): def test_normalize_defaults(self, settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
@ -273,12 +296,12 @@ class TestNamespace:
assert defaults_normalized.values.__dict__ == {} assert defaults_normalized.values.__dict__ == {}
non_defaults = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True) non_defaults = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
non_defaults.values.tst_test = 'world' non_defaults.values.tst__test = 'world'
non_defaults.values.tst_persistent_test = 'world' non_defaults.values.tst_persistent__test = 'world'
non_defaults_normalized = settngs_manager.get_namespace(settngs_manager.normalize_config(non_defaults, file=True, default=False), file=True, default=False) non_defaults_normalized = settngs_manager.get_namespace(settngs_manager.normalize_config(non_defaults, file=True, default=False), file=True, default=False)
assert non_defaults_normalized.values.tst_test == 'world' assert non_defaults_normalized.values.tst__test == 'world'
assert non_defaults_normalized.values.tst_persistent_test == 'world' assert non_defaults_normalized.values.tst_persistent__test == 'world'
def test_normalize(self, settngs_manager): def test_normalize(self, settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
@ -286,15 +309,37 @@ class TestNamespace:
defaults = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True) defaults = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
defaults.values.test = 'fail' # Not defined in settngs_manager, should be removed defaults.values.test = 'fail' # Not defined in settngs_manager, should be removed
defaults.values.persistent_hello = 'success' # Not defined in settngs_manager, should stay defaults.values.persistent__hello = 'success' # Not defined in settngs_manager, should stay
normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(defaults, file=True), file=True) normalized, _ = settngs_manager.get_namespace(settngs_manager.normalize_config(defaults, file=True), file=True)
assert not hasattr(normalized, 'test') assert not hasattr(normalized, 'test')
assert hasattr(normalized, 'tst_test') assert hasattr(normalized, 'tst__test')
assert normalized.tst_test == 'hello' assert normalized.tst__test == 'hello'
assert normalized.persistent_hello == 'success' assert normalized.persistent__hello == 'success'
assert normalized.persistent_world == 'world' assert normalized.persistent__world == 'world'
def test_normalize_unknown(self, settngs_manager):
manager = settngs.Manager()
manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
manager_unknown = settngs.Manager()
manager_unknown.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
# This manager doesn't know about this group
# manager_unknown.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
defaults = manager.get_namespace(manager.defaults(), file=True, cmdline=True)
defaults.values.test = 'fail' # Not defined in manager, should be removed
defaults.values.persistent__hello = 'success' # Not defined in manager, should stay
normalized, _ = manager_unknown.get_namespace(defaults.values, file=True)
assert not hasattr(normalized, 'test')
assert hasattr(normalized, 'tst__test')
assert normalized.tst__test == 'hello'
assert normalized.persistent__hello == 'success'
assert normalized.persistent__world == 'world'
def test_get_namespace_with_namespace(settngs_manager): def test_get_namespace_with_namespace(settngs_manager):
@ -306,7 +351,7 @@ def test_get_namespace_with_namespace(settngs_manager):
def test_get_namespace_group(settngs_manager): def test_get_namespace_group(settngs_manager):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello'))
defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True) defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True)
assert defaults.tst_test == 'hello' assert defaults.tst__test == 'hello'
def test_clean_config(settngs_manager): def test_clean_config(settngs_manager):
@ -337,8 +382,8 @@ def test_parse_cmdline(settngs_manager):
namespaces = ( namespaces = (
lambda definitions: settngs.Config({'tst': {'test': 'fail', 'test2': 'success'}}, definitions), 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: settngs.Config(argparse.Namespace(tst__test='fail', tst__test2='success'), definitions),
lambda definitions: argparse.Namespace(tst_test='fail', tst_test2='success'), lambda definitions: argparse.Namespace(tst__test='fail', tst__test2='success'),
) )