9 Commits

Author SHA1 Message Date
d284039f12 Update pre-commit 2025-08-30 20:26:32 -07:00
5fc7a5173d Update tests
Remove python3.8 leftovers
Add prog to create_argparser
Remove metavar usage from BooleanOptionalAction
2025-08-30 20:26:18 -07:00
ecda6fe610 Extract arguments for generic types 2025-08-30 20:07:43 -07:00
cfc55b7366 Drop python 3.8 2025-05-01 17:13:07 -07:00
6b158ca495 Handle lists of Enum 2025-05-01 17:01:34 -07:00
3b0ae0f24a [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0)
- [github.com/hhatto/autopep8: v2.2.0 → v2.3.1](https://github.com/hhatto/autopep8/compare/v2.2.0...v2.3.1)
- [github.com/PyCQA/flake8: 7.0.0 → 7.1.0](https://github.com/PyCQA/flake8/compare/7.0.0...7.1.0)
2024-06-24 17:18:37 +00:00
f1735c879d Don't inherit from argparse Namespace 2024-06-06 19:33:07 -07:00
afdc11eb6f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/reorder-python-imports: v3.12.0 → v3.13.0](https://github.com/asottile/reorder-python-imports/compare/v3.12.0...v3.13.0)
- [github.com/hhatto/autopep8: v2.1.1 → v2.2.0](https://github.com/hhatto/autopep8/compare/v2.1.1...v2.2.0)
2024-06-03 17:17:48 +00:00
9b71af1c75 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/hhatto/autopep8: v2.1.0 → v2.1.1](https://github.com/hhatto/autopep8/compare/v2.1.0...v2.1.1)
2024-05-27 17:15:51 +00:00
5 changed files with 134 additions and 229 deletions

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python_version: ['3.8', '3.9','3.10','3.11']
python_version: ['3.9','3.10','3.11','3.13']
os: [ubuntu-latest]
steps:

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@ -10,39 +10,39 @@ repos:
- id: name-tests-test
- id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0
rev: v2.8.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports
rev: v3.12.0
rev: v3.15.0
hooks:
- id: reorder-python-imports
args: [--py38-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma
rev: v3.1.0
rev: v3.2.0
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/asottile/dead
rev: v1.5.2
rev: v2.1.0
hooks:
- id: dead
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.2
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py38-plus]
exclude: tests
- repo: https://github.com/hhatto/autopep8
rev: v2.1.0
rev: v2.3.2
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
rev: 7.3.0
hooks:
- id: flake8
additional_dependencies: [flake8-encodings, flake8-warnings, flake8-builtins, flake8-print]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
rev: v1.17.1
hooks:
- id: mypy

View File

@ -10,10 +10,13 @@ import re
import sys
import typing
import warnings
from argparse import BooleanOptionalAction
from argparse import Namespace
from collections import defaultdict
from collections.abc import Sequence
from collections.abc import Set
from enum import Enum
from types import GenericAlias as types_GenericAlias
from typing import Any
from typing import Callable
from typing import cast
@ -38,72 +41,6 @@ else: # pragma: no cover
from typing import NamedTuple
if sys.version_info < (3, 9): # pragma: no cover
from typing import List
from typing import _GenericAlias as types_GenericAlias
def removeprefix(self: str, prefix: str, /) -> str:
if self.startswith(prefix):
return self[len(prefix):]
else:
return self[:]
def get_typing_type(t: type) -> type:
if t.__module__ == 'builtins':
if t is NoneType:
return None
return getattr(typing, t.__name__.title(), t)
return t
class BooleanOptionalAction(argparse.Action):
def __init__(
self,
option_strings,
dest,
default=None,
type=None, # noqa: A002
choices=None,
required=False,
help=None, # noqa: A002
metavar=None,
):
_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)
if option_string.startswith('--'):
option_string = '--no-' + option_string[2:]
_option_strings.append(option_string)
if help is not None and default is not None and default is not argparse.SUPPRESS:
help += ' (default: %(default)s)'
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)
def __call__(self, parser, namespace, values, option_string=None): # pragma: no cover dead: disable
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith('--no-'))
else: # pragma: no cover
List = list
from types import GenericAlias as types_GenericAlias
from argparse import BooleanOptionalAction
removeprefix = str.removeprefix
def get_typing_type(t: type) -> type | None:
return None if t is NoneType else t
def _isnamedtupleinstance(x: Any) -> bool: # pragma: no cover
t = type(x)
b = t.__bases__
@ -246,15 +183,14 @@ class Setting:
if isinstance(list_type, types_GenericAlias) and issubclass(list_type.__origin__, Collection):
return list_type, self.default is None
# Ensure that generic aliases work for python 3.8
if list_type is not None:
list_type = get_typing_type(list_type)
else:
list_type = get_typing_type(type(self.default))
if list_type is NoneType:
list_type = None
if list_type is None and self.default is not None:
list_type = type(self.default)
# Default to a list if we don't know what type of collection this is
if list_type is None or not issubclass(list_type, Collection):
list_type = List
if list_type is None or not issubclass(list_type, Collection) or issubclass(list_type, Enum):
list_type = list
# Get the item type (int) in list[int]
it = get_item_type(self.default)
@ -262,7 +198,7 @@ class Setting:
it = self.type
if it is self.__no_type:
return self._process_type() or List[str], self.default is None
return self._process_type() or list[str], self.default is None
# Try to get the generic alias for this type
if it is not None:
@ -273,7 +209,7 @@ class Setting:
...
# Fall back to list[str] if anything fails
return list_type[str], self.default is None # type: ignore[index]
return cast(type, list_type[str]), self.default is None # type: ignore[index]
except Exception:
return None, self.default is None
@ -283,7 +219,7 @@ class Setting:
if isinstance(self.type, type):
return self.type
return typing.get_type_hints(self.type).get('return', None) # type: ignore[no-any-return]
return cast(dict[str, type], typing.get_type_hints(self.type)).get('return', None)
def _guess_type_internal(self) -> tuple[type | str | None, bool]:
default_is_none = self.default is None
@ -294,7 +230,7 @@ class Setting:
'store_const': (type(self.const), default_is_none),
'count': (int, default_is_none),
'extend': self._guess_collection(),
'append_const': (List[type(self.const)], default_is_none), # type: ignore[misc]
'append_const': (cast(type, list[type(self.const)]), default_is_none), # type: ignore[misc]
'help': (None, default_is_none),
'version': (None, default_is_none),
}
@ -332,7 +268,7 @@ class Setting:
def _guess_type(self) -> tuple[type | str | None, bool]:
if self.action == 'append':
return List[self._guess_type_internal()[0]], self.default is None # type: ignore[misc]
return cast(type, list[self._guess_type_internal()[0]]), self.default is None # type: ignore[misc]
return self._guess_type_internal()
def get_dest(self, prefix: str, names: Sequence[str], dest: str | None) -> tuple[str, str, str, bool]:
@ -367,7 +303,7 @@ class Setting:
return self.argparse_args, self.filter_argparse_kwargs()
class TypedNS(Namespace):
class TypedNS():
def __init__(self) -> None:
raise TypeError('TypedNS cannot be instantiated')
@ -394,6 +330,17 @@ if TYPE_CHECKING:
ns = Union[TypedNS, Config[T], None]
def _get_import(t: type) -> tuple[str, str]:
type_name = t.__name__
import_needed = ''
# Builtin types don't need an import
if t.__module__ != 'builtins':
import_needed = f'import {t.__module__}'
# Use the full imported name
type_name = t.__module__ + '.' + type_name
return type_name, import_needed
def _type_to_string(t: type | str) -> tuple[str, str]:
type_name = 'Any'
import_needed = ''
@ -402,22 +349,23 @@ def _type_to_string(t: type | str) -> tuple[str, str]:
type_name = t
# Handle generic aliases eg dict[str, str] instead of dict
elif isinstance(t, types_GenericAlias):
if not get_args(t):
args = get_args(t)
if args:
import_needed = ''
for arg in args:
_, arg_import = _get_import(arg)
import_needed += '\n' + arg_import
else:
t = t.__origin__.__name__
type_name = str(t)
# Handle standard type objects
elif isinstance(t, type):
type_name = t.__name__
# Builtin types don't need an import
if t.__module__ != 'builtins':
import_needed = f'import {t.__module__}'
# Use the full imported name
type_name = t.__module__ + '.' + type_name
type_name, import_needed = _get_import(t)
# Expand Any to typing.Any
if type_name == 'Any':
type_name = 'typing.Any'
return type_name, import_needed
return type_name.strip(), import_needed.strip()
def generate_ns(definitions: Definitions) -> tuple[str, str]:
@ -549,7 +497,7 @@ def get_options(config: Config[T], group: str) -> dict[str, Any]:
if name in internal_names:
values[internal_names[name].dest] = value
else:
values[removeprefix(name, f'{group}').lstrip('_')] = value
values[name.removeprefix(f'{group}').lstrip('_')] = value
return values
@ -756,11 +704,12 @@ def save_file(
return True
def create_argparser(definitions: Definitions, description: str, epilog: str) -> argparse.ArgumentParser:
def create_argparser(definitions: Definitions, description: str, epilog: str, *, prog: str | None = None) -> argparse.ArgumentParser:
"""Creates an :class:`argparse.ArgumentParser` from all cmdline settings"""
groups: dict[str, ArgParser] = {}
argparser = argparse.ArgumentParser(
description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter,
prog=prog,
)
def get_current_group(setting: Setting) -> ArgParser:
@ -801,6 +750,8 @@ def parse_cmdline(
epilog: str,
args: list[str] | None = None,
config: ns[T] = None,
*,
prog: str | None = None,
) -> Config[Values]:
"""
Creates an `argparse.ArgumentParser` from cmdline settings in `self.definitions`.
@ -822,7 +773,7 @@ def parse_cmdline(
else:
namespace = config
argparser = create_argparser(definitions, description, epilog)
argparser = create_argparser(definitions, description, epilog, prog=prog)
ns = argparser.parse_args(args, namespace=namespace)
return normalize_config(Config(ns, definitions), cmdline=True, file=True)
@ -859,11 +810,12 @@ def parse_config(
class Manager:
"""docstring for Manager"""
def __init__(self, description: str = '', epilog: str = '', definitions: Definitions | Config[T] | None = None):
def __init__(self, description: str = '', epilog: str = '', definitions: Definitions | Config[T] | None = None, *, prog: str | None = None):
# This one is never used, it just makes MyPy happy
self.argparser = argparse.ArgumentParser(description=description, epilog=epilog)
self.description = description
self.epilog = epilog
self.prog = prog
self.definitions: Definitions
if isinstance(definitions, Config):
@ -886,7 +838,7 @@ class Manager:
return generate_dict(self.definitions)
def create_argparser(self) -> None:
self.argparser = create_argparser(self.definitions, self.description, self.epilog)
self.argparser = create_argparser(self.definitions, self.description, self.epilog, prog=self.prog)
def add_setting(self, *args: Any, **kwargs: Any) -> None:
"""Passes all arguments through to `Setting`, `group` and `exclusive` are already set"""
@ -1098,7 +1050,6 @@ def example_group(manager: Manager) -> None:
manager.add_setting(
'--verbose', '-v',
default=False,
metavar='nothing',
action=BooleanOptionalAction, # Added in Python 3.9
)

View File

@ -9,7 +9,6 @@ author_email = timmy@narnian.us
license = MIT
license_files = LICENSE
classifiers =
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
@ -19,7 +18,7 @@ classifiers =
packages = find:
install_requires =
typing-extensions>=4.3.0;python_version < '3.11'
python_requires = >=3.8
python_requires = >=3.9
include_package_data = True
[options.packages.find]
@ -31,7 +30,7 @@ exclude =
settngs = py.typed
[tox:tox]
envlist = py3.8,py3.9,py3.10,py3.11,py3.12,pypy3
envlist = py3.9,py3.10,py3.11,py3.12,py3.13,pypy3
[testenv]
deps = -rrequirements-dev.txt

View File

@ -2,11 +2,13 @@ from __future__ import annotations
import argparse
import ast
from enum import Enum, auto
import json
import pathlib
import sys
from collections import defaultdict
from typing import Generator
from typing import NamedTuple
import pytest
@ -29,19 +31,6 @@ positional arguments:
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 Set
help_output = '''\
usage: __main__.py [-h] [TEST [TEST ...]]
positional arguments:
TEST
optional arguments:
-h, --help show this help message and exit
'''
else: # pragma: no cover
List = list
Set = set
@ -126,12 +115,14 @@ def test_exclusive_group(settngs_manager):
with pytest.raises(SystemExit):
settngs_manager.add_group('tst', lambda parser: parser.add_setting('--test2', default='hello'), exclusive_group=True)
settngs_manager.create_argparser()
args = settngs_manager.argparser.parse_args(['--test', 'never', '--test2', 'never'])
args = settngs_manager.argparser.parse_args(['__main__.py', '--test', 'never', '--test2', 'never'])
def test_files_group(capsys, settngs_manager):
settngs_manager.add_group('runtime', lambda parser: parser.add_setting('test', default='hello', nargs='*'))
settngs_manager.prog = '__main__.py'
settngs_manager.create_argparser()
settngs_manager.prog = '__main__.py'
settngs_manager.argparser.print_help()
captured = capsys.readouterr()
assert captured.out == help_output
@ -139,7 +130,9 @@ def test_files_group(capsys, settngs_manager):
def test_setting_without_group(capsys, settngs_manager):
settngs_manager.add_setting('test', default='hello', nargs='*')
settngs_manager.prog = '__main__.py'
settngs_manager.create_argparser()
settngs_manager.prog = '__main__.py'
settngs_manager.argparser.print_help()
captured = capsys.readouterr()
assert captured.out == help_output
@ -613,6 +606,10 @@ def test_adding_to_existing_persistent_group(settngs_manager: settngs.Manager, t
assert default_to_regular(settngs_manager.definitions) == default_to_regular(settngs_manager2.definitions)
class test_enum(Enum):
test = auto()
class test_type(int):
...
@ -673,28 +670,31 @@ types = (
(6, settngs.Setting('-t', '--test', action='append'), List[str], True),
(7, settngs.Setting('-t', '--test', action='extend'), List[str], True),
(8, settngs.Setting('-t', '--test', nargs='+'), List[str], True),
(9, settngs.Setting('-t', '--test', action='store_const', const=1), int, True),
(10, settngs.Setting('-t', '--test', action='append_const', const=1), List[int], True),
(11, settngs.Setting('-t', '--test', action='store_true'), bool, False),
(12, settngs.Setting('-t', '--test', action='store_false'), bool, False),
(13, settngs.Setting('-t', '--test', action=settngs.BooleanOptionalAction), bool, True),
(14, settngs.Setting('-t', '--test', action=_customAction), 'Any', True),
(15, settngs.Setting('-t', '--test', action='help'), None, True),
(16, settngs.Setting('-t', '--test', action='version'), None, True),
(17, settngs.Setting('-t', '--test', type=int), int, True),
(18, settngs.Setting('-t', '--test', type=int, nargs='+'), List[int], True),
(19, settngs.Setting('-t', '--test', type=_typed_function), test_type, True),
(20, settngs.Setting('-t', '--test', type=_untyped_function, default=1), int, False),
(21, settngs.Setting('-t', '--test', type=_untyped_function, default=[1]), List[int], False),
(22, settngs.Setting('-t', '--test', type=_untyped_function), 'Any', True),
(23, settngs.Setting('-t', '--test', type=_untyped_function, default={1}), Set[int], False),
(24, settngs.Setting('-t', '--test', action='append', type=int), List[int], True),
(25, settngs.Setting('-t', '--test', action='extend', type=int, nargs=2), List[int], True),
(26, settngs.Setting('-t', '--test', action='append', type=int, nargs=2), List[List[int]], True),
(27, settngs.Setting('-t', '--test', action='extend', nargs='+'), List[str], True),
(28, settngs.Setting('-t', '--test', action='extend', type=_typed_list_generic_function), List[test_type], True),
(29, settngs.Setting('-t', '--test', action='extend', type=_typed_list_function), List, True),
(30, settngs.Setting('-t', '--test', action='extend', type=_typed_set_function), Set, True),
(9, settngs.Setting('-t', '--test', nargs='+', type=pathlib.Path), List[pathlib.Path], True),
(10, settngs.Setting('-t', '--test', nargs='+', type=test_enum), List[test_enum], True),
(11, settngs.Setting('-t', '--test', action='store_const', const=1), int, True),
(12, settngs.Setting('-t', '--test', action='append_const', const=1), List[int], True),
(13, settngs.Setting('-t', '--test', action='store_true'), bool, False),
(14, settngs.Setting('-t', '--test', action='store_false'), bool, False),
(15, settngs.Setting('-t', '--test', action=argparse.BooleanOptionalAction), bool, True),
(16, settngs.Setting('-t', '--test', action=_customAction), 'Any', True),
(17, settngs.Setting('-t', '--test', type=test_enum), test_enum, True),
(18, settngs.Setting('-t', '--test', type=int), int, True),
(19, settngs.Setting('-t', '--test', type=int, nargs='+'), List[int], True),
(20, settngs.Setting('-t', '--test', type=_typed_function), test_type, True),
(21, settngs.Setting('-t', '--test', type=_untyped_function, default=1), int, False),
(22, settngs.Setting('-t', '--test', type=_untyped_function, default=[1]), List[int], False),
(23, settngs.Setting('-t', '--test', type=_untyped_function), 'Any', True),
(24, settngs.Setting('-t', '--test', type=_untyped_function, default={1}), Set[int], False),
(25, settngs.Setting('-t', '--test', action='append', type=int), List[int], True),
(26, settngs.Setting('-t', '--test', action='extend', type=int, nargs=2), List[int], True),
(27, settngs.Setting('-t', '--test', action='append', type=int, nargs=2), List[List[int]], True),
(28, settngs.Setting('-t', '--test', action='extend', nargs='+'), List[str], True),
(29, settngs.Setting('-t', '--test', action='extend', type=_typed_list_generic_function), List[test_type], True),
(30, settngs.Setting('-t', '--test', action='extend', type=_typed_list_function), List, True),
(31, settngs.Setting('-t', '--test', action='extend', type=_typed_set_function), Set, True),
(32, settngs.Setting('-t', '--test', action='help'), None, True),
(33, settngs.Setting('-t', '--test', action='version'), None, True),
)
@ -706,6 +706,11 @@ def test_guess_type(num, setting, typ, noneable_expected):
assert noneable == noneable_expected
class TypeResult(NamedTuple):
extra_imports: str
typ: str
expected_src = '''from __future__ import annotations
import settngs
@ -714,46 +719,40 @@ import settngs
class SettngsNS(settngs.TypedNS):
test__test: {typ}
'''
no_type_expected_src = '''from __future__ import annotations
import settngs
class SettngsNS(settngs.TypedNS):
...
'''
settings = (
(0, lambda parser: parser.add_setting('-t', '--test'), expected_src.format(extra_imports='', typ='str | None')),
(1, lambda parser: parser.add_setting('-t', '--test', cmdline=False), expected_src.format(extra_imports='import typing\n', typ='typing.Any')),
(2, lambda parser: parser.add_setting('-t', '--test', default=1, file=True, cmdline=False), expected_src.format(extra_imports='', typ='int')),
(3, lambda parser: parser.add_setting('-t', '--test', default='test'), expected_src.format(extra_imports='', typ='str')),
(4, lambda parser: parser.add_setting('-t', '--test', default='test', file=True, cmdline=False), expected_src.format(extra_imports='', typ='str')),
(5, lambda parser: parser.add_setting('-t', '--test', action='count'), expected_src.format(extra_imports='', typ='int | None')),
(6, lambda parser: parser.add_setting('-t', '--test', action='append'), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[str]} | None')),
(7, lambda parser: parser.add_setting('-t', '--test', action='extend'), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[str]} | None')),
(8, lambda parser: parser.add_setting('-t', '--test', nargs='+'), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[str]} | None')),
(9, lambda parser: parser.add_setting('-t', '--test', action='store_const', const=1), expected_src.format(extra_imports='', typ='int | None')),
(10, lambda parser: parser.add_setting('-t', '--test', action='append_const', const=1), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[int]} | None')),
(11, lambda parser: parser.add_setting('-t', '--test', action='store_true'), expected_src.format(extra_imports='', typ='bool')),
(12, lambda parser: parser.add_setting('-t', '--test', action='store_false'), expected_src.format(extra_imports='', typ='bool')),
(13, lambda parser: parser.add_setting('-t', '--test', action=settngs.BooleanOptionalAction), expected_src.format(extra_imports='', typ='bool | None')),
(14, lambda parser: parser.add_setting('-t', '--test', action=_customAction), expected_src.format(extra_imports='import typing\n', typ='typing.Any')),
(15, lambda parser: parser.add_setting('-t', '--test', action='help'), no_type_expected_src),
(16, lambda parser: parser.add_setting('-t', '--test', action='version'), no_type_expected_src),
(17, lambda parser: parser.add_setting('-t', '--test', type=int), expected_src.format(extra_imports='', typ='int | None')),
(18, lambda parser: parser.add_setting('-t', '--test', type=int, nargs='+'), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[int]} | None')),
(19, lambda parser: parser.add_setting('-t', '--test', type=_typed_function), expected_src.format(extra_imports='import tests.settngs_test\n', typ='tests.settngs_test.test_type | None')),
(20, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=1), expected_src.format(extra_imports='', typ='int')),
(21, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=[1]), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[int]}')),
(22, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function), expected_src.format(extra_imports='import typing\n', typ='typing.Any')),
(23, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default={1}), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{Set[int]}')),
(24, lambda parser: parser.add_setting('-t', '--test', action='append', type=int), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[int]} | None')),
(25, lambda parser: parser.add_setting('-t', '--test', action='extend', type=int, nargs=2), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[int]} | None')),
(26, lambda parser: parser.add_setting('-t', '--test', action='append', type=int, nargs=2), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[List[int]]} | None')),
(27, lambda parser: parser.add_setting('-t', '--test', action='extend', nargs='+'), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[str]} | None')),
(28, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_generic_function), expected_src.format(extra_imports='import typing\n' if sys.version_info < (3, 9) else '', typ=f'{List[test_type]} | None')),
(29, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_function), expected_src.format(extra_imports='', typ=f'{settngs._type_to_string(List)[0]} | None')),
(30, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_set_function), expected_src.format(extra_imports='', typ=f'{settngs._type_to_string(Set)[0]} | None')),
(0, lambda parser: parser.add_setting('-t', '--test'), TypeResult(extra_imports='', typ='str | None')),
(1, lambda parser: parser.add_setting('-t', '--test', cmdline=False), TypeResult(extra_imports='import typing\n', typ='typing.Any')),
(2, lambda parser: parser.add_setting('-t', '--test', default=1, file=True, cmdline=False), TypeResult(extra_imports='', typ='int')),
(3, lambda parser: parser.add_setting('-t', '--test', default='test'), TypeResult(extra_imports='', typ='str')),
(4, lambda parser: parser.add_setting('-t', '--test', default='test', file=True, cmdline=False), TypeResult(extra_imports='', typ='str')),
(5, lambda parser: parser.add_setting('-t', '--test', action='count'), TypeResult(extra_imports='', typ='int | None')),
(6, lambda parser: parser.add_setting('-t', '--test', action='append'), TypeResult(extra_imports='', typ=f'{List[str]} | None')),
(7, lambda parser: parser.add_setting('-t', '--test', action='extend'), TypeResult(extra_imports='', typ=f'{List[str]} | None')),
(8, lambda parser: parser.add_setting('-t', '--test', nargs='+'), TypeResult(extra_imports='', typ=f'{List[str]} | None')),
(9, lambda parser: parser.add_setting('-t', '--test', nargs='+', type=pathlib.Path), TypeResult(extra_imports='import pathlib._local\n' if sys.version_info[:2] == (3, 13) else 'import pathlib\n', typ=f'{List[pathlib.Path]} | None')),
(10, lambda parser: parser.add_setting('-t', '--test', nargs='+', type=test_enum), TypeResult(extra_imports='import tests.settngs_test\n', typ=f'{List[test_enum]} | None')),
(11, lambda parser: parser.add_setting('-t', '--test', action='store_const', const=1), TypeResult(extra_imports='', typ='int | None')),
(12, lambda parser: parser.add_setting('-t', '--test', action='append_const', const=1), TypeResult(extra_imports='', typ=f'{List[int]} | None')),
(13, lambda parser: parser.add_setting('-t', '--test', action='store_true'), TypeResult(extra_imports='', typ='bool')),
(14, lambda parser: parser.add_setting('-t', '--test', action='store_false'), TypeResult(extra_imports='', typ='bool')),
(15, lambda parser: parser.add_setting('-t', '--test', action=argparse.BooleanOptionalAction), TypeResult(extra_imports='', typ='bool | None')),
(16, lambda parser: parser.add_setting('-t', '--test', action=_customAction), TypeResult(extra_imports='import typing\n', typ='typing.Any')),
(17, lambda parser: parser.add_setting('-t', '--test', type=test_enum), TypeResult(extra_imports='import tests.settngs_test\n', typ='tests.settngs_test.test_enum | None')),
(18, lambda parser: parser.add_setting('-t', '--test', type=int), TypeResult(extra_imports='', typ='int | None')),
(19, lambda parser: parser.add_setting('-t', '--test', type=int, nargs='+'), TypeResult(extra_imports='', typ=f'{List[int]} | None')),
(20, lambda parser: parser.add_setting('-t', '--test', type=_typed_function), TypeResult(extra_imports='import tests.settngs_test\n', typ='tests.settngs_test.test_type | None')),
(21, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=1), TypeResult(extra_imports='', typ='int')),
(22, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=[1]), TypeResult(extra_imports='', typ=f'{List[int]}')),
(23, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function), TypeResult(extra_imports='import typing\n', typ='typing.Any')),
(24, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default={1}), TypeResult(extra_imports='', typ=f'{Set[int]}')),
(25, lambda parser: parser.add_setting('-t', '--test', action='append', type=int), TypeResult(extra_imports='', typ=f'{List[int]} | None')),
(26, lambda parser: parser.add_setting('-t', '--test', action='extend', type=int, nargs=2), TypeResult(extra_imports='', typ=f'{List[int]} | None')),
(27, lambda parser: parser.add_setting('-t', '--test', action='append', type=int, nargs=2), TypeResult(extra_imports='', typ=f'{List[List[int]]} | None')),
(28, lambda parser: parser.add_setting('-t', '--test', action='extend', nargs='+'), TypeResult(extra_imports='', typ=f'{List[str]} | None')),
(29, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_generic_function), TypeResult(extra_imports='import tests.settngs_test\n', typ=f'{List[test_type]} | None')),
(30, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_function), TypeResult(extra_imports='', typ=f'{settngs._type_to_string(List)[0]} | None')),
(31, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_set_function), TypeResult(extra_imports='', typ=f'{settngs._type_to_string(Set)[0]} | None')),
)
@ -764,14 +763,13 @@ def test_generate_ns(settngs_manager, num, set_options, expected):
imports, types = settngs_manager.generate_ns()
generated_src = '\n\n\n'.join((imports, types))
assert generated_src == expected
assert generated_src == expected_src.format(**expected._asdict())
ast.parse(generated_src)
expected_src_dict = '''from __future__ import annotations
import typing
{extra_imports}
class test(typing.TypedDict):
@ -781,61 +779,18 @@ class test(typing.TypedDict):
class SettngsDict(typing.TypedDict):
test: test
'''
no_type_expected_src_dict = '''from __future__ import annotations
import typing
class test(typing.TypedDict):
...
class SettngsDict(typing.TypedDict):
test: test
'''
settings_dict = (
(0, lambda parser: parser.add_setting('-t', '--test'), expected_src_dict.format(extra_imports='', typ='str | None')),
(1, lambda parser: parser.add_setting('-t', '--test', cmdline=False), expected_src_dict.format(extra_imports='', typ='typing.Any')),
(2, lambda parser: parser.add_setting('-t', '--test', default=1, file=True, cmdline=False), expected_src_dict.format(extra_imports='', typ='int')),
(3, lambda parser: parser.add_setting('-t', '--test', default='test'), expected_src_dict.format(extra_imports='', typ='str')),
(4, lambda parser: parser.add_setting('-t', '--test', default='test', file=True, cmdline=False), expected_src_dict.format(extra_imports='', typ='str')),
(5, lambda parser: parser.add_setting('-t', '--test', action='count'), expected_src_dict.format(extra_imports='', typ='int | None')),
(6, lambda parser: parser.add_setting('-t', '--test', action='append'), expected_src_dict.format(extra_imports='', typ=f'{List[str]} | None')),
(7, lambda parser: parser.add_setting('-t', '--test', action='extend'), expected_src_dict.format(extra_imports='', typ=f'{List[str]} | None')),
(8, lambda parser: parser.add_setting('-t', '--test', nargs='+'), expected_src_dict.format(extra_imports='', typ=f'{List[str]} | None')),
(9, lambda parser: parser.add_setting('-t', '--test', action='store_const', const=1), expected_src_dict.format(extra_imports='', typ='int | None')),
(10, lambda parser: parser.add_setting('-t', '--test', action='append_const', const=1), expected_src_dict.format(extra_imports='', typ=f'{List[int]} | None')),
(11, lambda parser: parser.add_setting('-t', '--test', action='store_true'), expected_src_dict.format(extra_imports='', typ='bool')),
(12, lambda parser: parser.add_setting('-t', '--test', action='store_false'), expected_src_dict.format(extra_imports='', typ='bool')),
(13, lambda parser: parser.add_setting('-t', '--test', action=settngs.BooleanOptionalAction), expected_src_dict.format(extra_imports='', typ='bool | None')),
(14, lambda parser: parser.add_setting('-t', '--test', action=_customAction), expected_src_dict.format(extra_imports='', typ='typing.Any')),
(15, lambda parser: parser.add_setting('-t', '--test', action='help'), no_type_expected_src_dict),
(16, lambda parser: parser.add_setting('-t', '--test', action='version'), no_type_expected_src_dict),
(17, lambda parser: parser.add_setting('-t', '--test', type=int), expected_src_dict.format(extra_imports='', typ='int | None')),
(18, lambda parser: parser.add_setting('-t', '--test', type=int, nargs='+'), expected_src_dict.format(extra_imports='', typ=f'{List[int]} | None')),
(19, lambda parser: parser.add_setting('-t', '--test', type=_typed_function), expected_src_dict.format(extra_imports='import tests.settngs_test\n', typ=f'{test_type.__module__}.{test_type.__name__} | None')),
(20, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=1), expected_src_dict.format(extra_imports='', typ='int')),
(21, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default=[1]), expected_src_dict.format(extra_imports='', typ=f'{List[int]}')),
(22, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function), expected_src_dict.format(extra_imports='', typ='typing.Any')),
(23, lambda parser: parser.add_setting('-t', '--test', type=_untyped_function, default={1}), expected_src_dict.format(extra_imports='', typ=f'{Set[int]}')),
(24, lambda parser: parser.add_setting('-t', '--test', action='append', type=int), expected_src_dict.format(extra_imports='', typ=f'{List[int]} | None')),
(25, lambda parser: parser.add_setting('-t', '--test', action='extend', type=int, nargs=2), expected_src_dict.format(extra_imports='', typ=f'{List[int]} | None')),
(26, lambda parser: parser.add_setting('-t', '--test', action='append', type=int, nargs=2), expected_src_dict.format(extra_imports='', typ=f'{List[List[int]]} | None')),
(27, lambda parser: parser.add_setting('-t', '--test', action='extend', nargs='+'), expected_src_dict.format(extra_imports='', typ=f'{List[str]} | None')),
(28, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_generic_function), expected_src_dict.format(extra_imports='', typ=f'{List[test_type]} | None')),
(29, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_list_function), expected_src_dict.format(extra_imports='', typ=f'{settngs._type_to_string(List)[0]} | None')),
(30, lambda parser: parser.add_setting('-t', '--test', action='extend', type=_typed_set_function), expected_src_dict.format(extra_imports='', typ=f'{settngs._type_to_string(Set)[0]} | None')),
)
@pytest.mark.parametrize('num,set_options,expected', settings_dict)
@pytest.mark.parametrize('num,set_options,expected', settings)
def test_generate_dict(settngs_manager, num, set_options, expected):
settngs_manager.add_group('test', set_options)
imports, types = settngs_manager.generate_dict()
generated_src = '\n\n\n'.join((imports, types))
assert generated_src == expected
if 'import typing' not in expected.extra_imports:
expected = TypeResult('import typing\n' + expected.extra_imports, expected.typ)
assert generated_src == expected_src_dict.format(**expected._asdict())
ast.parse(generated_src)