Core: typing for `Option.default` and a few other ClassVars (#2899)
* Core: typing for `Option.default` and a few other `Option` class variables This is a replacement for https://github.com/ArchipelagoMW/Archipelago/pull/2173 You can read discussion there for issues we found for why we can't have more specific typing on `default` instead of setting a default in `Option` (where we don't know the type), we check in the metaclass to make sure they have a default. * NumericOption doesn't need the type annotation that brings out the mypy bug * SoE default ClassVar
This commit is contained in:
		
							parent
							
								
									f8d5fe0e1e
								
							
						
					
					
						commit
						03d403ff51
					
				
							
								
								
									
										20
									
								
								Options.py
								
								
								
								
							
							
						
						
									
										20
									
								
								Options.py
								
								
								
								
							| 
						 | 
					@ -41,6 +41,11 @@ class AssembleOptions(abc.ABCMeta):
 | 
				
			||||||
        aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
 | 
					        aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
 | 
				
			||||||
                   name.startswith("alias_")}
 | 
					                   name.startswith("alias_")}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert (
 | 
				
			||||||
 | 
					            name in {"Option", "VerifyKeys"} or  # base abstract classes don't need default
 | 
				
			||||||
 | 
					            "default" in attrs or
 | 
				
			||||||
 | 
					            any(hasattr(base, "default") for base in bases)
 | 
				
			||||||
 | 
					        ), f"Option class {name} needs default value"
 | 
				
			||||||
        assert "random" not in aliases, "Choice option 'random' cannot be manually assigned."
 | 
					        assert "random" not in aliases, "Choice option 'random' cannot be manually assigned."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # auto-alias Off and On being parsed as True and False
 | 
					        # auto-alias Off and On being parsed as True and False
 | 
				
			||||||
| 
						 | 
					@ -96,7 +101,7 @@ T = typing.TypeVar('T')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Option(typing.Generic[T], metaclass=AssembleOptions):
 | 
					class Option(typing.Generic[T], metaclass=AssembleOptions):
 | 
				
			||||||
    value: T
 | 
					    value: T
 | 
				
			||||||
    default = 0
 | 
					    default: typing.ClassVar[typing.Any]  # something that __init__ will be able to convert to the correct type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # convert option_name_long into Name Long as display_name, otherwise name_long is the result.
 | 
					    # convert option_name_long into Name Long as display_name, otherwise name_long is the result.
 | 
				
			||||||
    # Handled in get_option_name()
 | 
					    # Handled in get_option_name()
 | 
				
			||||||
| 
						 | 
					@ -106,8 +111,9 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
 | 
				
			||||||
    supports_weighting = True
 | 
					    supports_weighting = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # filled by AssembleOptions:
 | 
					    # filled by AssembleOptions:
 | 
				
			||||||
    name_lookup: typing.Dict[T, str]
 | 
					    name_lookup: typing.ClassVar[typing.Dict[T, str]]  # type: ignore
 | 
				
			||||||
    options: typing.Dict[str, int]
 | 
					    # https://github.com/python/typing/discussions/1460 the reason for this type: ignore
 | 
				
			||||||
 | 
					    options: typing.ClassVar[typing.Dict[str, int]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self) -> str:
 | 
					    def __repr__(self) -> str:
 | 
				
			||||||
        return f"{self.__class__.__name__}({self.current_option_name})"
 | 
					        return f"{self.__class__.__name__}({self.current_option_name})"
 | 
				
			||||||
| 
						 | 
					@ -160,6 +166,8 @@ class FreeText(Option[str]):
 | 
				
			||||||
    """Text option that allows users to enter strings.
 | 
					    """Text option that allows users to enter strings.
 | 
				
			||||||
    Needs to be validated by the world or option definition."""
 | 
					    Needs to be validated by the world or option definition."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, value: str):
 | 
					    def __init__(self, value: str):
 | 
				
			||||||
        assert isinstance(value, str), "value of FreeText must be a string"
 | 
					        assert isinstance(value, str), "value of FreeText must be a string"
 | 
				
			||||||
        self.value = value
 | 
					        self.value = value
 | 
				
			||||||
| 
						 | 
					@ -811,7 +819,7 @@ class VerifyKeys(metaclass=FreezeValidKeys):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]):
 | 
					class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]):
 | 
				
			||||||
    default: typing.Dict[str, typing.Any] = {}
 | 
					    default = {}
 | 
				
			||||||
    supports_weighting = False
 | 
					    supports_weighting = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, value: typing.Dict[str, typing.Any]):
 | 
					    def __init__(self, value: typing.Dict[str, typing.Any]):
 | 
				
			||||||
| 
						 | 
					@ -852,7 +860,7 @@ 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.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = ()
 | 
					    default = ()
 | 
				
			||||||
    supports_weighting = False
 | 
					    supports_weighting = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, value: typing.Iterable[typing.Any]):
 | 
					    def __init__(self, value: typing.Iterable[typing.Any]):
 | 
				
			||||||
| 
						 | 
					@ -878,7 +886,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OptionSet(Option[typing.Set[str]], VerifyKeys):
 | 
					class OptionSet(Option[typing.Set[str]], VerifyKeys):
 | 
				
			||||||
    default: typing.Union[typing.Set[str], typing.FrozenSet[str]] = frozenset()
 | 
					    default = frozenset()
 | 
				
			||||||
    supports_weighting = False
 | 
					    supports_weighting = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, value: typing.Iterable[str]):
 | 
					    def __init__(self, value: typing.Iterable[str]):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
from dataclasses import dataclass, fields
 | 
					from dataclasses import dataclass, fields
 | 
				
			||||||
from typing import Any, cast, Dict, Iterator, List, Tuple, Protocol
 | 
					from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
 | 
					from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
 | 
				
			||||||
    ProgressionBalancing, Range, Toggle
 | 
					    ProgressionBalancing, Range, Toggle
 | 
				
			||||||
| 
						 | 
					@ -8,13 +8,13 @@ from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option,
 | 
				
			||||||
# typing boilerplate
 | 
					# typing boilerplate
 | 
				
			||||||
class FlagsProtocol(Protocol):
 | 
					class FlagsProtocol(Protocol):
 | 
				
			||||||
    value: int
 | 
					    value: int
 | 
				
			||||||
    default: int
 | 
					    default: ClassVar[int]
 | 
				
			||||||
    flags: List[str]
 | 
					    flags: List[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FlagProtocol(Protocol):
 | 
					class FlagProtocol(Protocol):
 | 
				
			||||||
    value: int
 | 
					    value: int
 | 
				
			||||||
    default: int
 | 
					    default: ClassVar[int]
 | 
				
			||||||
    flag: str
 | 
					    flag: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue