Skip to content

Commit

Permalink
Core: typing for Option.default and a few other Option class vari…
Browse files Browse the repository at this point in the history
…ables

This is a replacement for ArchipelagoMW#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.
  • Loading branch information
beauxq committed Mar 5, 2024
1 parent 37a871e commit 2006da7
Showing 1 changed file with 15 additions and 7 deletions.
22 changes: 15 additions & 7 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def __new__(mcs, name, bases, attrs):
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
Expand Down Expand Up @@ -96,7 +101,7 @@ def meta__init__(self, *args, **kwargs):

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()
Expand All @@ -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})"
Expand Down Expand Up @@ -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
Expand All @@ -182,7 +190,7 @@ def get_option_name(cls, value: str) -> str:


class NumericOption(Option[int], numbers.Integral, abc.ABC):
default = 0
default: typing.ClassVar[typing.Union[int, typing.Literal["random"]]] = 0

# 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
Expand Down Expand Up @@ -803,7 +811,7 @@ def verify(self, world: typing.Type[World], player_name: str, plando_options: "P


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]):
Expand Down Expand Up @@ -844,7 +852,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[str]):
Expand All @@ -870,7 +878,7 @@ def __contains__(self, item):


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]):
Expand Down

0 comments on commit 2006da7

Please sign in to comment.