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:
Doug Hoskisson 2024-03-12 14:03:57 -07:00 committed by GitHub
parent f8d5fe0e1e
commit 03d403ff51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 17 additions and 9 deletions

View File

@ -41,6 +41,11 @@ class AssembleOptions(abc.ABCMeta):
aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if
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."
# 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):
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.
# Handled in get_option_name()
@ -106,8 +111,9 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
supports_weighting = True
# filled by AssembleOptions:
name_lookup: typing.Dict[T, str]
options: typing.Dict[str, int]
name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore
# https://github.com/python/typing/discussions/1460 the reason for this type: ignore
options: typing.ClassVar[typing.Dict[str, int]]
def __repr__(self) -> str:
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.
Needs to be validated by the world or option definition."""
default = ""
def __init__(self, value: str):
assert isinstance(value, str), "value of FreeText must be a string"
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]):
default: typing.Dict[str, typing.Any] = {}
default = {}
supports_weighting = False
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.
# 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
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):
default: typing.Union[typing.Set[str], typing.FrozenSet[str]] = frozenset()
default = frozenset()
supports_weighting = False
def __init__(self, value: typing.Iterable[str]):

View File

@ -1,5 +1,5 @@
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, \
ProgressionBalancing, Range, Toggle
@ -8,13 +8,13 @@ from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option,
# typing boilerplate
class FlagsProtocol(Protocol):
value: int
default: int
default: ClassVar[int]
flags: List[str]
class FlagProtocol(Protocol):
value: int
default: int
default: ClassVar[int]
flag: str