Compare commits

...

2 Commits

Author SHA1 Message Date
Timmy Welch
cfc55b7366 Drop python 3.8 2025-05-01 17:13:07 -07:00
Timmy Welch
6b158ca495 Handle lists of Enum 2025-05-01 17:01:34 -07:00
3 changed files with 16 additions and 80 deletions

View File

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

View File

@ -10,10 +10,13 @@ import re
import sys import sys
import typing import typing
import warnings import warnings
from argparse import BooleanOptionalAction
from argparse import Namespace from argparse import Namespace
from collections import defaultdict from collections import defaultdict
from collections.abc import Sequence from collections.abc import Sequence
from collections.abc import Set from collections.abc import Set
from enum import Enum
from types import GenericAlias as types_GenericAlias
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import cast from typing import cast
@ -38,72 +41,6 @@ else: # pragma: no cover
from typing import NamedTuple 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 def _isnamedtupleinstance(x: Any) -> bool: # pragma: no cover
t = type(x) t = type(x)
b = t.__bases__ b = t.__bases__
@ -246,15 +183,14 @@ class Setting:
if isinstance(list_type, types_GenericAlias) and issubclass(list_type.__origin__, Collection): if isinstance(list_type, types_GenericAlias) and issubclass(list_type.__origin__, Collection):
return list_type, self.default is None return list_type, self.default is None
# Ensure that generic aliases work for python 3.8 if list_type is NoneType:
if list_type is not None: list_type = None
list_type = get_typing_type(list_type) if list_type is None and self.default is not None:
else: list_type = type(self.default)
list_type = get_typing_type(type(self.default))
# Default to a list if we don't know what type of collection this is # 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): if list_type is None or not issubclass(list_type, Collection) or issubclass(list_type, Enum):
list_type = List list_type = list
# Get the item type (int) in list[int] # Get the item type (int) in list[int]
it = get_item_type(self.default) it = get_item_type(self.default)
@ -262,7 +198,7 @@ class Setting:
it = self.type it = self.type
if it is self.__no_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 # Try to get the generic alias for this type
if it is not None: if it is not None:
@ -294,7 +230,7 @@ class Setting:
'store_const': (type(self.const), default_is_none), 'store_const': (type(self.const), default_is_none),
'count': (int, default_is_none), 'count': (int, default_is_none),
'extend': self._guess_collection(), 'extend': self._guess_collection(),
'append_const': (List[type(self.const)], default_is_none), # type: ignore[misc] 'append_const': (list[type(self.const)], default_is_none), # type: ignore[misc]
'help': (None, default_is_none), 'help': (None, default_is_none),
'version': (None, default_is_none), 'version': (None, default_is_none),
} }
@ -332,7 +268,7 @@ class Setting:
def _guess_type(self) -> tuple[type | str | None, bool]: def _guess_type(self) -> tuple[type | str | None, bool]:
if self.action == 'append': if self.action == 'append':
return List[self._guess_type_internal()[0]], self.default is None # type: ignore[misc] return list[self._guess_type_internal()[0]], self.default is None # type: ignore[misc]
return self._guess_type_internal() return self._guess_type_internal()
def get_dest(self, prefix: str, names: Sequence[str], dest: str | None) -> tuple[str, str, str, bool]: def get_dest(self, prefix: str, names: Sequence[str], dest: str | None) -> tuple[str, str, str, bool]:
@ -549,7 +485,7 @@ 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}').lstrip('_')] = value values[name.removeprefix(f'{group}').lstrip('_')] = value
return values return values

View File

@ -19,7 +19,7 @@ classifiers =
packages = find: packages = find:
install_requires = install_requires =
typing-extensions>=4.3.0;python_version < '3.11' typing-extensions>=4.3.0;python_version < '3.11'
python_requires = >=3.8 python_requires = >=3.9
include_package_data = True include_package_data = True
[options.packages.find] [options.packages.find]
@ -31,7 +31,7 @@ exclude =
settngs = py.typed settngs = py.typed
[tox:tox] [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] [testenv]
deps = -rrequirements-dev.txt deps = -rrequirements-dev.txt