Core: Allow common collections in OptionSet and OptionList constructors (#2874)
* allow common collection in set and list option constructors * allow any iterable of strings * add return None --------- Co-authored-by: beauxq <beauxq@yahoo.com>
This commit is contained in:
parent
113c54f9be
commit
37a871eab1
25
Options.py
25
Options.py
|
@ -1,19 +1,18 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import logging
|
|
||||||
from copy import deepcopy
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import functools
|
import functools
|
||||||
|
import logging
|
||||||
import math
|
import math
|
||||||
import numbers
|
import numbers
|
||||||
import random
|
import random
|
||||||
import typing
|
import typing
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from schema import And, Optional, Or, Schema
|
from schema import And, Optional, Or, Schema
|
||||||
|
|
||||||
from Utils import get_fuzzy_results
|
from Utils import get_fuzzy_results, is_iterable_of_str
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from BaseClasses import PlandoOptions
|
from BaseClasses import PlandoOptions
|
||||||
|
@ -59,6 +58,7 @@ class AssembleOptions(abc.ABCMeta):
|
||||||
def verify(self, *args, **kwargs) -> None:
|
def verify(self, *args, **kwargs) -> None:
|
||||||
for f in verifiers:
|
for f in verifiers:
|
||||||
f(self, *args, **kwargs)
|
f(self, *args, **kwargs)
|
||||||
|
|
||||||
attrs["verify"] = verify
|
attrs["verify"] = verify
|
||||||
else:
|
else:
|
||||||
assert verifiers, "class Option is supposed to implement def verify"
|
assert verifiers, "class Option is supposed to implement def verify"
|
||||||
|
@ -183,6 +183,7 @@ class FreeText(Option[str]):
|
||||||
|
|
||||||
class NumericOption(Option[int], numbers.Integral, abc.ABC):
|
class NumericOption(Option[int], numbers.Integral, abc.ABC):
|
||||||
default = 0
|
default = 0
|
||||||
|
|
||||||
# note: some of the `typing.Any`` here is a result of unresolved issue in python standards
|
# note: some of the `typing.Any`` here is a result of unresolved issue in python standards
|
||||||
# `int` is not a `numbers.Integral` according to the official typestubs
|
# `int` is not a `numbers.Integral` according to the official typestubs
|
||||||
# (even though isinstance(5, numbers.Integral) == True)
|
# (even though isinstance(5, numbers.Integral) == True)
|
||||||
|
@ -598,7 +599,7 @@ class PlandoBosses(TextChoice, metaclass=BossMeta):
|
||||||
if isinstance(self.value, int):
|
if isinstance(self.value, int):
|
||||||
return
|
return
|
||||||
from BaseClasses import PlandoOptions
|
from BaseClasses import PlandoOptions
|
||||||
if not(PlandoOptions.bosses & plando_options):
|
if not (PlandoOptions.bosses & plando_options):
|
||||||
# plando is disabled but plando options were given so pull the option and change it to an int
|
# plando is disabled but plando options were given so pull the option and change it to an int
|
||||||
option = self.value.split(";")[-1]
|
option = self.value.split(";")[-1]
|
||||||
self.value = self.options[option]
|
self.value = self.options[option]
|
||||||
|
@ -765,7 +766,7 @@ class VerifyKeys(metaclass=FreezeValidKeys):
|
||||||
value: typing.Any
|
value: typing.Any
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_keys(cls, data: typing.List[str]):
|
def verify_keys(cls, data: typing.Iterable[str]) -> None:
|
||||||
if cls.valid_keys:
|
if cls.valid_keys:
|
||||||
data = set(data)
|
data = set(data)
|
||||||
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
|
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
|
||||||
|
@ -843,11 +844,11 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
||||||
# If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead.
|
# If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead.
|
||||||
# Not a docstring so it doesn't get grabbed by the options system.
|
# Not a docstring so it doesn't get grabbed by the options system.
|
||||||
|
|
||||||
default: typing.List[typing.Any] = []
|
default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = ()
|
||||||
supports_weighting = False
|
supports_weighting = False
|
||||||
|
|
||||||
def __init__(self, value: typing.List[typing.Any]):
|
def __init__(self, value: typing.Iterable[str]):
|
||||||
self.value = deepcopy(value)
|
self.value = list(deepcopy(value))
|
||||||
super(OptionList, self).__init__()
|
super(OptionList, self).__init__()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -856,7 +857,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: typing.Any):
|
def from_any(cls, data: typing.Any):
|
||||||
if type(data) == list:
|
if is_iterable_of_str(data):
|
||||||
cls.verify_keys(data)
|
cls.verify_keys(data)
|
||||||
return cls(data)
|
return cls(data)
|
||||||
return cls.from_text(str(data))
|
return cls.from_text(str(data))
|
||||||
|
@ -882,7 +883,7 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: typing.Any):
|
def from_any(cls, data: typing.Any):
|
||||||
if isinstance(data, (list, set, frozenset)):
|
if is_iterable_of_str(data):
|
||||||
cls.verify_keys(data)
|
cls.verify_keys(data)
|
||||||
return cls(data)
|
return cls(data)
|
||||||
return cls.from_text(str(data))
|
return cls.from_text(str(data))
|
||||||
|
@ -932,7 +933,7 @@ class OptionsMetaProperty(type):
|
||||||
bases: typing.Tuple[type, ...],
|
bases: typing.Tuple[type, ...],
|
||||||
attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty":
|
attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty":
|
||||||
for attr_type in attrs.values():
|
for attr_type in attrs.values():
|
||||||
assert not isinstance(attr_type, AssembleOptions),\
|
assert not isinstance(attr_type, AssembleOptions), \
|
||||||
f"Options for {name} should be type hinted on the class, not assigned"
|
f"Options for {name} should be type hinted on the class, not assigned"
|
||||||
return super().__new__(mcs, name, bases, attrs)
|
return super().__new__(mcs, name, bases, attrs)
|
||||||
|
|
||||||
|
|
11
Utils.py
11
Utils.py
|
@ -19,6 +19,7 @@ import warnings
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from settings import Settings, get_settings
|
from settings import Settings, get_settings
|
||||||
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union
|
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union
|
||||||
|
from typing_extensions import TypeGuard
|
||||||
from yaml import load, load_all, dump
|
from yaml import load, load_all, dump
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -966,3 +967,13 @@ class RepeatableChain:
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return sum(len(iterable) for iterable in self.iterable)
|
return sum(len(iterable) for iterable in self.iterable)
|
||||||
|
|
||||||
|
|
||||||
|
def is_iterable_of_str(obj: object) -> TypeGuard[typing.Iterable[str]]:
|
||||||
|
""" but not a `str` (because technically, `str` is `Iterable[str]`) """
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return False
|
||||||
|
if not isinstance(obj, typing.Iterable):
|
||||||
|
return False
|
||||||
|
obj_it: typing.Iterable[object] = obj
|
||||||
|
return all(isinstance(v, str) for v in obj_it)
|
||||||
|
|
|
@ -11,3 +11,4 @@ certifi>=2023.11.17
|
||||||
cython>=3.0.8
|
cython>=3.0.8
|
||||||
cymem>=2.0.8
|
cymem>=2.0.8
|
||||||
orjson>=3.9.10
|
orjson>=3.9.10
|
||||||
|
typing-extensions>=4.7.0
|
||||||
|
|
Loading…
Reference in New Issue