6 Commits
0.9.0 ... 0.9.3

Author SHA1 Message Date
7c748f6815 Merge branch 'pre-commit-ci-update-config' 2024-02-22 14:44:51 -08:00
8d5b30546e Improve type guessing for generic Sequence types 2024-02-22 14:42:07 -08:00
cebca481fc [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.15.0 → v3.15.1](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.1)
2024-02-19 17:21:12 +00:00
dd8cd1188e [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0)
- [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.8.0)
2024-01-08 17:17:39 +00:00
d30e73a679 Do not add duplicate settings when generating a namespace 2023-12-17 18:26:57 -08:00
fc2a175e5b Fix normalization of settings using a custom dest 2023-12-17 16:09:41 -08:00
3 changed files with 84 additions and 22 deletions

View File

@ -28,7 +28,7 @@ repos:
hooks: hooks:
- id: dead - id: dead
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.15.0 rev: v3.15.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py38-plus]
@ -37,11 +37,11 @@ repos:
hooks: hooks:
- id: autopep8 - id: autopep8
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.1.0 rev: 7.0.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-encodings, flake8-warnings, flake8-builtins, flake8-length, flake8-print] additional_dependencies: [flake8-encodings, flake8-warnings, flake8-builtins, flake8-length, flake8-print]
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.7.0 rev: v1.8.0
hooks: hooks:
- id: mypy - id: mypy

View File

@ -13,6 +13,7 @@ from collections import defaultdict
from collections.abc import Sequence from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import cast
from typing import Dict from typing import Dict
from typing import Generic from typing import Generic
from typing import NoReturn from typing import NoReturn
@ -84,6 +85,20 @@ else: # pragma: no cover
removeprefix = str.removeprefix removeprefix = str.removeprefix
def _isnamedtupleinstance(x: Any) -> bool:
t = type(x)
b = t.__bases__
if len(b) != 1 or b[0] != tuple:
return False
f = getattr(t, '_fields', None)
if not isinstance(f, tuple):
return False
return all(isinstance(n, str) for n in f)
class Setting: class Setting:
def __init__( def __init__(
self, self,
@ -199,6 +214,11 @@ class Setting:
return str return str
else: else:
if not self.cmdline and self.default is not None: if not self.cmdline and self.default is not None:
if not isinstance(self.default, str) and not _isnamedtupleinstance(self.default) and isinstance(self.default, Sequence) and self.default and self.default[0]:
try:
return cast(type, type(self.default)[type(self.default[0])])
except Exception:
...
return type(self.default) return type(self.default)
return 'Any' return 'Any'
@ -211,6 +231,11 @@ class Setting:
t: type | str = type_hints['return'] t: type | str = type_hints['return']
return t return t
if self.default is not None: if self.default is not None:
if not isinstance(self.default, str) and not _isnamedtupleinstance(self.default) and isinstance(self.default, Sequence) and self.default and self.default[0]:
try:
return cast(type, type(self.default)[type(self.default[0])])
except Exception:
...
return type(self.default) return type(self.default)
return 'Any' return 'Any'
@ -324,7 +349,9 @@ def generate_ns(definitions: Definitions) -> str:
if type_name == 'Any': if type_name == 'Any':
type_name = 'typing.Any' type_name = 'typing.Any'
attributes.append(f' {setting.internal_name}: {type_name}') attribute = f' {setting.internal_name}: {type_name}'
if attribute not in attributes:
attributes.append(attribute)
# Add a blank line between groups # Add a blank line between groups
if attributes and attributes[-1] != '': if attributes and attributes[-1] != '':
attributes.append('') attributes.append('')
@ -448,13 +475,13 @@ def normalize_config(
value, is_default = get_option(options, setting) value, is_default = get_option(options, setting)
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
group_options[setting_name] = value group_options[setting.dest] = value
elif setting_name in group_options: elif setting.dest in group_options:
# default values have been requested to be removed # default values have been requested to be removed
del group_options[setting_name] del group_options[setting.dest]
elif setting_name in group_options: elif setting.dest in group_options:
# 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.dest]
normalized[group_name] = group_options normalized[group_name] = group_options
return Config(normalized, config.definitions) return Config(normalized, config.definitions)

View File

@ -18,7 +18,18 @@ from testing.settngs import failure
from testing.settngs import success from testing.settngs import success
if sys.version_info < (3, 9): # pragma: no cover if sys.version_info >= (3, 10): # pragma: no cover
List = list
help_output = '''\
usage: __main__.py [-h] [TEST ...]
positional arguments:
TEST
options:
-h, --help show this help message and exit
'''
elif sys.version_info < (3, 9): # pragma: no cover
from typing import List from typing import List
help_output = '''\ help_output = '''\
usage: __main__.py [-h] [TEST [TEST ...]] usage: __main__.py [-h] [TEST [TEST ...]]
@ -42,17 +53,6 @@ optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
''' '''
if sys.version_info >= (3, 10): # pragma: no cover
help_output = '''\
usage: __main__.py [-h] [TEST ...]
positional arguments:
TEST
options:
-h, --help show this help message and exit
'''
@pytest.fixture @pytest.fixture
def settngs_manager() -> Generator[settngs.Manager, None, None]: def settngs_manager() -> Generator[settngs.Manager, None, None]:
@ -210,6 +210,24 @@ class TestValues:
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_dest(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('--test2', dest='test', default='hello'))
settngs_manager.add_persistent_group('tst_persistent', lambda parser: parser.add_setting('--test', default='hello'))
defaults = settngs_manager.defaults()
defaults_normalized = settngs_manager.normalize_config(defaults, file=True, default=False)
assert defaults_normalized.values['tst'] == {}
assert defaults_normalized.values['tst_persistent'] == {}
non_defaults = settngs_manager.defaults()
non_defaults.values['tst']['test'] = 'world'
non_defaults.values['tst_persistent']['test'] = 'world'
non_defaults_normalized = settngs_manager.normalize_config(non_defaults, file=True, default=False)
assert non_defaults_normalized.values['tst'] == {'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'))
settngs_manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world')) settngs_manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
@ -317,6 +335,23 @@ class TestNamespace:
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_dest(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('--test2', dest='test', default='hello'))
settngs_manager.add_persistent_group('tst_persistent', lambda parser: parser.add_setting('--test', default='hello'))
defaults = settngs_manager.defaults()
defaults_normalized = settngs_manager.get_namespace(settngs_manager.normalize_config(defaults, file=True, default=False), file=True, default=False)
assert defaults_normalized.values.__dict__ == {}
non_defaults = settngs_manager.get_namespace(settngs_manager.defaults(), file=True, cmdline=True)
non_defaults.values.tst__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)
assert non_defaults_normalized.values.tst__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'))
settngs_manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world')) settngs_manager.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))
@ -333,7 +368,7 @@ class TestNamespace:
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): def test_normalize_unknown_group(self, settngs_manager):
manager = settngs.Manager() manager = settngs.Manager()
manager.add_group('tst', lambda parser: parser.add_setting('--test', default='hello')) 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.add_persistent_group('persistent', lambda parser: parser.add_setting('--world', default='world'))