Options: Always verify keys for VerifyKeys options (#3280)
* Options: Always verify keys for VerifyKeys options * fix PlandoTexts * use OptionError and give a slightly better error message for which option it is * add the player name to the error * don't create an unnecessary list --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
parent
91f7cf16de
commit
53bc4ffa52
38
Options.py
38
Options.py
|
@ -786,17 +786,22 @@ class VerifyKeys(metaclass=FreezeValidKeys):
|
||||||
verify_location_name: bool = False
|
verify_location_name: bool = False
|
||||||
value: typing.Any
|
value: typing.Any
|
||||||
|
|
||||||
@classmethod
|
def verify_keys(self) -> None:
|
||||||
def verify_keys(cls, data: typing.Iterable[str]) -> None:
|
if self.valid_keys:
|
||||||
if cls.valid_keys:
|
data = set(self.value)
|
||||||
data = set(data)
|
dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data)
|
||||||
dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data)
|
extra = dataset - self._valid_keys
|
||||||
extra = dataset - cls._valid_keys
|
|
||||||
if extra:
|
if extra:
|
||||||
raise Exception(f"Found unexpected key {', '.join(extra)} in {cls}. "
|
raise OptionError(
|
||||||
f"Allowed keys: {cls._valid_keys}.")
|
f"Found unexpected key {', '.join(extra)} in {getattr(self, 'display_name', self)}. "
|
||||||
|
f"Allowed keys: {self._valid_keys}."
|
||||||
|
)
|
||||||
|
|
||||||
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
def verify(self, world: typing.Type[World], player_name: str, plando_options: "PlandoOptions") -> None:
|
||||||
|
try:
|
||||||
|
self.verify_keys()
|
||||||
|
except OptionError as validation_error:
|
||||||
|
raise OptionError(f"Player {player_name} has invalid option keys:\n{validation_error}")
|
||||||
if self.convert_name_groups and self.verify_item_name:
|
if self.convert_name_groups and self.verify_item_name:
|
||||||
new_value = type(self.value)() # empty container of whatever value is
|
new_value = type(self.value)() # empty container of whatever value is
|
||||||
for item_name in self.value:
|
for item_name in self.value:
|
||||||
|
@ -833,7 +838,6 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mappin
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict:
|
def from_any(cls, data: typing.Dict[str, typing.Any]) -> OptionDict:
|
||||||
if type(data) == dict:
|
if type(data) == dict:
|
||||||
cls.verify_keys(data)
|
|
||||||
return cls(data)
|
return cls(data)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}")
|
raise NotImplementedError(f"Cannot Convert from non-dictionary, got {type(data)}")
|
||||||
|
@ -879,7 +883,6 @@ 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 is_iterable_except_str(data):
|
if is_iterable_except_str(data):
|
||||||
cls.verify_keys(data)
|
|
||||||
return cls(data)
|
return cls(data)
|
||||||
return cls.from_text(str(data))
|
return cls.from_text(str(data))
|
||||||
|
|
||||||
|
@ -905,7 +908,6 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: typing.Any):
|
def from_any(cls, data: typing.Any):
|
||||||
if is_iterable_except_str(data):
|
if is_iterable_except_str(data):
|
||||||
cls.verify_keys(data)
|
|
||||||
return cls(data)
|
return cls(data)
|
||||||
return cls.from_text(str(data))
|
return cls.from_text(str(data))
|
||||||
|
|
||||||
|
@ -948,6 +950,19 @@ class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys):
|
||||||
self.value = []
|
self.value = []
|
||||||
logging.warning(f"The plando texts module is turned off, "
|
logging.warning(f"The plando texts module is turned off, "
|
||||||
f"so text for {player_name} will be ignored.")
|
f"so text for {player_name} will be ignored.")
|
||||||
|
else:
|
||||||
|
super().verify(world, player_name, plando_options)
|
||||||
|
|
||||||
|
def verify_keys(self) -> None:
|
||||||
|
if self.valid_keys:
|
||||||
|
data = set(text.at for text in self)
|
||||||
|
dataset = set(word.casefold() for word in data) if self.valid_keys_casefold else set(data)
|
||||||
|
extra = dataset - self._valid_keys
|
||||||
|
if extra:
|
||||||
|
raise OptionError(
|
||||||
|
f"Invalid \"at\" placement {', '.join(extra)} in {getattr(self, 'display_name', self)}. "
|
||||||
|
f"Allowed placements: {self._valid_keys}."
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_any(cls, data: PlandoTextsFromAnyType) -> Self:
|
def from_any(cls, data: PlandoTextsFromAnyType) -> Self:
|
||||||
|
@ -971,7 +986,6 @@ class PlandoTexts(Option[typing.List[PlandoText]], VerifyKeys):
|
||||||
texts.append(text)
|
texts.append(text)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}")
|
raise Exception(f"Cannot create plando text from non-dictionary type, got {type(text)}")
|
||||||
cls.verify_keys([text.at for text in texts])
|
|
||||||
return cls(texts)
|
return cls(texts)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}")
|
raise NotImplementedError(f"Cannot Convert from non-list, got {type(data)}")
|
||||||
|
|
|
@ -6,7 +6,7 @@ from argparse import Namespace
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any
|
from typing import Dict, ClassVar, Iterable, Tuple, Optional, List, Union, Any
|
||||||
|
|
||||||
from BaseClasses import MultiWorld, CollectionState, get_seed, Location, Item, ItemClassification
|
from BaseClasses import MultiWorld, CollectionState, PlandoOptions, get_seed, Location, Item, ItemClassification
|
||||||
from Options import VerifyKeys
|
from Options import VerifyKeys
|
||||||
from test.bases import WorldTestBase
|
from test.bases import WorldTestBase
|
||||||
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
from test.general import gen_steps, setup_solo_multiworld as setup_base_solo_multiworld
|
||||||
|
@ -365,7 +365,7 @@ def setup_solo_multiworld(test_options: Optional[Dict[Union[str, StardewValleyOp
|
||||||
|
|
||||||
if issubclass(option, VerifyKeys):
|
if issubclass(option, VerifyKeys):
|
||||||
# Values should already be verified, but just in case...
|
# Values should already be verified, but just in case...
|
||||||
option.verify_keys(value.value)
|
value.verify(StardewValleyWorld, "Tester", PlandoOptions.bosses)
|
||||||
|
|
||||||
setattr(args, name, {1: value})
|
setattr(args, name, {1: value})
|
||||||
multiworld.set_options(args)
|
multiworld.set_options(args)
|
||||||
|
|
Loading…
Reference in New Issue