Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
101eef56ca | |||
2f000e12f3 | |||
0301e1698c | |||
6af1e3c562 | |||
fe3bce42fd | |||
4c41e6f588 |
@ -10,38 +10,38 @@ repos:
|
||||
- id: name-tests-test
|
||||
- id: requirements-txt-fixer
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.3.0
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.9.0
|
||||
rev: v3.10.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py38-plus, --add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v2.4.0
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
args: [--py36-plus]
|
||||
- repo: https://github.com/asottile/dead
|
||||
rev: v1.5.1
|
||||
rev: v1.5.2
|
||||
hooks:
|
||||
- id: dead
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.4.0
|
||||
rev: v3.10.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/pre-commit/mirrors-autopep8
|
||||
rev: v2.0.2
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.0.4
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
rev: 6.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-encodings, flake8-warnings, flake8-builtins, flake8-length, flake8-print]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.3.0
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
|
@ -141,10 +141,9 @@ class Setting:
|
||||
metavar = dest.upper()
|
||||
|
||||
# If we are not a flag, no '--' or '-' in front
|
||||
# we prefix the first name with the group as argparse sets dest to args[0]
|
||||
# I believe internal name may be able to be used here
|
||||
# we use internal_name as argparse sets dest to args[0]
|
||||
if not flag:
|
||||
args = tuple((f'{group}_{names[0]}'.lstrip('_'), *names[1:]))
|
||||
args = tuple((self.internal_name, *names[1:]))
|
||||
|
||||
self.action = action
|
||||
self.nargs = nargs
|
||||
@ -190,6 +189,8 @@ class Setting:
|
||||
def _guess_type(self) -> type | Literal['Any'] | None:
|
||||
if self.type is None and self.action is None:
|
||||
if self.cmdline:
|
||||
if self.nargs in ('+', '*') or isinstance(self.nargs, int) and self.nargs > 1:
|
||||
return List[str]
|
||||
return str
|
||||
else:
|
||||
if not self.cmdline and self.default is not None:
|
||||
@ -230,6 +231,7 @@ class Setting:
|
||||
dest_name = None
|
||||
flag = False
|
||||
|
||||
prefix = sanitize_name(prefix)
|
||||
for n in names:
|
||||
if n.startswith('--'):
|
||||
flag = True
|
||||
@ -255,6 +257,11 @@ class Setting:
|
||||
return self.argparse_args, self.filter_argparse_kwargs()
|
||||
|
||||
|
||||
class TypedNS:
|
||||
def __init__(self) -> None:
|
||||
raise TypeError('TypedNS cannot be instantiated')
|
||||
|
||||
|
||||
class Group(NamedTuple):
|
||||
persistent: bool
|
||||
v: dict[str, Setting]
|
||||
@ -263,7 +270,7 @@ class Group(NamedTuple):
|
||||
Values = Dict[str, Dict[str, Any]]
|
||||
Definitions = Dict[str, Group]
|
||||
|
||||
T = TypeVar('T', Values, Namespace)
|
||||
T = TypeVar('T', bound=Union[Values, Namespace, TypedNS])
|
||||
|
||||
|
||||
class Config(NamedTuple, Generic[T]):
|
||||
@ -273,12 +280,12 @@ class Config(NamedTuple, Generic[T]):
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ArgParser = Union[argparse._MutuallyExclusiveGroup, argparse._ArgumentGroup, argparse.ArgumentParser]
|
||||
ns = Namespace | Config[T] | None
|
||||
ns = Namespace | TypedNS | Config[T] | None
|
||||
|
||||
|
||||
def generate_ns(definitions: Definitions) -> str:
|
||||
imports = ['from __future__ import annotations', 'import typing', 'import settngs']
|
||||
ns = 'class settngs_namespace(settngs.Namespace):\n'
|
||||
ns = 'class settngs_namespace(settngs.TypedNS):\n'
|
||||
types = []
|
||||
for group_name, group in definitions.items():
|
||||
for setting_name, setting in group.v.items():
|
||||
@ -288,7 +295,7 @@ def generate_ns(definitions: Definitions) -> str:
|
||||
type_name = 'Any'
|
||||
if isinstance(t, str):
|
||||
type_name = t
|
||||
elif type(t) == types_GenericAlias:
|
||||
elif isinstance(t, types_GenericAlias):
|
||||
type_name = str(t)
|
||||
elif isinstance(t, type):
|
||||
type_name = t.__name__
|
||||
@ -313,7 +320,7 @@ def sanitize_name(name: str) -> str:
|
||||
return re.sub('[' + re.escape(' -_,.!@#$%^&*(){}[]\',."<>;:') + ']+', '_', name).strip('_')
|
||||
|
||||
|
||||
def get_option(options: Values | Namespace, setting: Setting) -> tuple[Any, bool]:
|
||||
def get_option(options: Values | Namespace | TypedNS, setting: Setting) -> tuple[Any, bool]:
|
||||
"""
|
||||
Helper function to retrieve the value for a setting and if the current value is the default value
|
||||
|
||||
@ -337,7 +344,7 @@ def get_options(config: Config[T], group: str) -> dict[str, Any]:
|
||||
group: The name of the group to retrieve
|
||||
"""
|
||||
if isinstance(config[0], dict):
|
||||
values = config[0].get(group, {}).copy()
|
||||
values: dict[str, Any] = config[0].get(group, {}).copy()
|
||||
else:
|
||||
internal_names = {x.internal_name: x for x in config[1][group].v.values()}
|
||||
values = {}
|
||||
@ -470,11 +477,14 @@ def get_namespace(
|
||||
if not file and not cmdline:
|
||||
raise ValueError('Invalid parameters: you must set either file or cmdline to True')
|
||||
|
||||
if isinstance(config.values, Namespace):
|
||||
options: Values
|
||||
definitions: Definitions
|
||||
if isinstance(config.values, dict):
|
||||
options = config.values
|
||||
definitions = config.definitions
|
||||
else:
|
||||
cfg = normalize_config(config, file=file, cmdline=cmdline, default=default, persistent=persistent)
|
||||
options, definitions = cfg
|
||||
else:
|
||||
options, definitions = config
|
||||
namespace = Namespace()
|
||||
for group_name, group in definitions.items():
|
||||
|
||||
@ -573,7 +583,7 @@ def parse_cmdline(
|
||||
args: Passed to argparse.ArgumentParser.parse_args
|
||||
config: The Config or Namespace object to use as a Namespace passed to argparse.ArgumentParser.parse_args
|
||||
"""
|
||||
namespace = None
|
||||
namespace: Namespace | TypedNS | None = None
|
||||
if isinstance(config, Config):
|
||||
if isinstance(config.values, Namespace):
|
||||
namespace = config.values
|
||||
@ -581,6 +591,7 @@ def parse_cmdline(
|
||||
namespace = get_namespace(config, file=True, cmdline=True, default=False)[0]
|
||||
else:
|
||||
namespace = config
|
||||
|
||||
argparser = create_argparser(definitions, description, epilog)
|
||||
ns = argparser.parse_args(args, namespace=namespace)
|
||||
|
||||
@ -866,7 +877,7 @@ def _main(args: list[str] | None = None) -> None:
|
||||
settings_path = pathlib.Path('./settings.json')
|
||||
manager = Manager(description='This is an example', epilog='goodbye!')
|
||||
|
||||
manager.add_group('example', example_group)
|
||||
manager.add_group('Example Group', example_group)
|
||||
manager.add_persistent_group('persistent', persistent_group)
|
||||
|
||||
file_config, success = manager.parse_file(settings_path)
|
||||
@ -875,14 +886,14 @@ def _main(args: list[str] | None = None) -> None:
|
||||
merged_config = manager.parse_cmdline(args=args, config=file_namespace)
|
||||
merged_namespace = manager.get_namespace(merged_config, file=True, cmdline=True)
|
||||
|
||||
print(f'Hello {merged_config.values["example"]["hello"]}') # noqa: T201
|
||||
if merged_namespace.values.example_save:
|
||||
print(f'Hello {merged_config.values["Example Group"]["hello"]}') # noqa: T201
|
||||
if merged_namespace.values.Example_Group_save:
|
||||
if manager.save_file(merged_config, settings_path):
|
||||
print(f'Successfully saved settings to {settings_path}') # noqa: T201
|
||||
else:
|
||||
print(f'Failed saving settings to a {settings_path}') # noqa: T201
|
||||
if merged_namespace.values.example_verbose:
|
||||
print(f'{merged_namespace.values.example_verbose=}') # noqa: T201
|
||||
if merged_namespace.values.Example_Group_verbose:
|
||||
print(f'{merged_namespace.values.Example_Group_verbose=}') # noqa: T201
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -15,52 +15,52 @@ example: list[tuple[list[str], str, str]] = [
|
||||
(
|
||||
['--hello', 'lordwelch', '-s'],
|
||||
'Hello lordwelch\nSuccessfully saved settings to settings.json\n',
|
||||
'{\n "example": {\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',
|
||||
),
|
||||
(
|
||||
[],
|
||||
'Hello lordwelch\n',
|
||||
'{\n "example": {\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'],
|
||||
'Hello lordwelch\nmerged_namespace.values.example_verbose=True\n',
|
||||
'{\n "example": {\n "hello": "lordwelch",\n "verbose": false\n },\n "persistent": {\n "test": false\n }\n}\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',
|
||||
),
|
||||
(
|
||||
['-v', '-s'],
|
||||
'Hello lordwelch\nSuccessfully saved settings to settings.json\nmerged_namespace.values.example_verbose=True\n',
|
||||
'{\n "example": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\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',
|
||||
),
|
||||
(
|
||||
[],
|
||||
'Hello lordwelch\nmerged_namespace.values.example_verbose=True\n',
|
||||
'{\n "example": {\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',
|
||||
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false\n }\n}\n',
|
||||
),
|
||||
(
|
||||
['manual settings.json'],
|
||||
'Hello lordwelch\nmerged_namespace.values.example_verbose=True\n',
|
||||
'{\n "example": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\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',
|
||||
),
|
||||
(
|
||||
['--no-verbose', '-t'],
|
||||
'Hello lordwelch\n',
|
||||
'{\n "example": {\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',
|
||||
),
|
||||
(
|
||||
['--no-verbose', '-s', '-t'],
|
||||
'Hello lordwelch\nSuccessfully saved settings to settings.json\n',
|
||||
'{\n "example": {\n "hello": "lordwelch",\n "verbose": false\n },\n "persistent": {\n "test": true,\n "hello": "world"\n }\n}\n',
|
||||
'{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": false\n },\n "persistent": {\n "test": true,\n "hello": "world"\n }\n}\n',
|
||||
),
|
||||
(
|
||||
['--hello', 'world', '--no-verbose', '--no-test', '-s'],
|
||||
'Hello world\nSuccessfully saved settings to settings.json\n',
|
||||
'{\n "example": {\n "hello": "world",\n "verbose": false\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n',
|
||||
'{\n "Example Group": {\n "hello": "world",\n "verbose": false\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n',
|
||||
),
|
||||
(
|
||||
[],
|
||||
'Hello world\n',
|
||||
'{\n "example": {\n "hello": "world",\n "verbose": false\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n',
|
||||
'{\n "Example Group": {\n "hello": "world",\n "verbose": false\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n',
|
||||
),
|
||||
]
|
||||
success = [
|
||||
|
@ -19,7 +19,7 @@ from testing.settngs import success
|
||||
|
||||
if sys.version_info < (3, 9): # pragma: no cover
|
||||
from typing import List
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
List = list
|
||||
|
||||
|
||||
@ -81,6 +81,11 @@ class TestValues:
|
||||
defaults, _ = settngs_manager.defaults()
|
||||
assert defaults['tst']['test'] == 'hello'
|
||||
|
||||
def test_get_defaults_group_space(self, settngs_manager):
|
||||
settngs_manager.add_group('Testing tst', lambda parser: parser.add_setting('--test', default='hello'))
|
||||
defaults, _ = settngs_manager.defaults()
|
||||
assert defaults['Testing tst']['test'] == 'hello'
|
||||
|
||||
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('tst2', lambda parser: parser.add_setting('--test2', default='hello', cmdline=False))
|
||||
@ -161,6 +166,11 @@ class TestNamespace:
|
||||
defaults, _ = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
|
||||
assert defaults.tst_test == 'hello'
|
||||
|
||||
def test_get_defaults_group_space(self, settngs_manager):
|
||||
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)
|
||||
assert defaults.Testing_tst_test == 'hello'
|
||||
|
||||
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('tst2', lambda parser: parser.add_setting('--test2', default='hello', cmdline=False))
|
||||
@ -541,7 +551,7 @@ import settngs
|
||||
if typ == 'tests.settngs_test.test_type':
|
||||
src += 'import tests.settngs_test\n'
|
||||
src += '''
|
||||
class settngs_namespace(settngs.Namespace):
|
||||
class settngs_namespace(settngs.TypedNS):
|
||||
'''
|
||||
if typ is None:
|
||||
src += ' ...\n'
|
||||
@ -564,7 +574,7 @@ def test_example(capsys, tmp_path, monkeypatch):
|
||||
for args, expected_out, expected_file in example:
|
||||
if args == ['manual settings.json']:
|
||||
settings_file.unlink()
|
||||
settings_file.write_text('{\n "example": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n')
|
||||
settings_file.write_text('{\n "Example Group": {\n "hello": "lordwelch",\n "verbose": true\n },\n "persistent": {\n "test": false,\n "hello": "world"\n }\n}\n')
|
||||
i += 1
|
||||
continue
|
||||
else:
|
||||
|
Reference in New Issue
Block a user