This commit is contained in:
Eric Froemling 2020-04-30 00:37:31 -07:00
parent 48f72ec123
commit f26954f330
2 changed files with 20 additions and 15 deletions

View File

@ -27,9 +27,9 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Dict, Type, Tuple from typing import Any, Dict, Type, Tuple
# For fields with these type strings, we require a passed value's type # For fields with these string types, we require a passed value's type
# to exactly match one of the tuple values to consider the assignment valid. # to exactly match one of the tuple values to consider the assignment valid.
_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = { _SIMPLE_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
'bool': (bool, ), 'bool': (bool, ),
'str': (str, ), 'str': (str, ),
'int': (int, ), 'int': (int, ),
@ -55,9 +55,9 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
An AttributeError will be raised if attributes are passed which are An AttributeError will be raised if attributes are passed which are
not present on the dataclass as fields. not present on the dataclass as fields.
This function may be significantly slower than simply passing dict This function may add significant overhead compared to passing dict
values to a dataclass' constructor or other more direct methods, but values to a dataclass' constructor or other more direct methods, but
the increased safety checks may be worth the extra overhead in some the increased safety checks may be worth the speed tradeoff in some
cases. cases.
""" """
if not dataclasses.is_dataclass(instance): if not dataclasses.is_dataclass(instance):
@ -72,8 +72,8 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
f" no '{key}' field.") f" no '{key}' field.")
field = fieldsdict[key] field = fieldsdict[key]
# We expect to be operating with 'from __future__ import annotations' # We expect to be operating under 'from __future__ import annotations'
# so this should always be a string for us; not an actual type. # so field types should always be strings for us; not an actual types.
# Complain if we come across an actual type. # Complain if we come across an actual type.
fieldtype: str = field.type # type: ignore fieldtype: str = field.type # type: ignore
if not isinstance(fieldtype, str): if not isinstance(fieldtype, str):
@ -82,11 +82,10 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
f' been created without "from __future__ import annotations";' f' been created without "from __future__ import annotations";'
f' those dataclasses are unsupported here.') f' those dataclasses are unsupported here.')
reqtypes = _ASSIGN_TYPES.get(fieldtype) reqtypes = _SIMPLE_ASSIGN_TYPES.get(fieldtype)
if reqtypes is not None: if reqtypes is not None:
# pylint: disable=unidiomatic-typecheck # pylint: disable=unidiomatic-typecheck
if not any(type(value) is t for t in reqtypes): if not any(type(value) is t for t in reqtypes):
# if not isinstance(value, reqtype):
if len(reqtypes) == 1: if len(reqtypes) == 1:
expected = reqtypes[0].__name__ expected = reqtypes[0].__name__
else: else:
@ -105,8 +104,8 @@ def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
def dataclass_validate(instance: Any) -> None: def dataclass_validate(instance: Any) -> None:
"""Ensure values in a dataclass are correct types. """Ensure values in a dataclass are correct types.
Note that this will always fail if a dataclass has value types Note that this will always fail if a dataclass contains field types
unsupported by this module. not supported by this module.
""" """
# We currently simply operate by grabbing dataclass values as a dict # We currently simply operate by grabbing dataclass values as a dict
# and passing them through dataclass_assign(). # and passing them through dataclass_assign().

View File

@ -38,9 +38,9 @@ sys.path += [
str(Path(os.getcwd(), 'dist', 'ba_data', 'python-site-packages')) str(Path(os.getcwd(), 'dist', 'ba_data', 'python-site-packages'))
] ]
from efro.dataclassutils import dataclass_assign, dataclass_validate
from bacommon.servermanager import (ServerConfig, ServerCommand, from bacommon.servermanager import (ServerConfig, ServerCommand,
make_server_command) make_server_command)
from efro.dataclassutils import dataclass_assign
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, List, Dict from typing import Optional, List, Dict
@ -50,8 +50,8 @@ if TYPE_CHECKING:
class ServerManagerApp: class ServerManagerApp:
"""An app which manages BallisticaCore server execution. """An app which manages BallisticaCore server execution.
Handles configuring, launching, re-launching, and controlling Handles configuring, launching, re-launching, and otherwise
BallisticaCore binaries operating in server mode. managing a BallisticaCore binary operating as a server.
""" """
def __init__(self) -> None: def __init__(self) -> None:
@ -82,11 +82,16 @@ class ServerManagerApp:
"""The current config settings for the app.""" """The current config settings for the app."""
return self._config return self._config
@config.setter
def config(self, value: ServerConfig) -> None:
dataclass_validate(value)
self._config = value
def _load_config(self) -> ServerConfig: def _load_config(self) -> ServerConfig:
user_config_path = 'config.yaml' user_config_path = 'config.yaml'
# Start with a default config, and if there is a config.yaml, # Start with a default config, and if there is a config.yaml,
# override parts of it. # assign whatever is contained within.
config = ServerConfig() config = ServerConfig()
if os.path.exists(user_config_path): if os.path.exists(user_config_path):
import yaml import yaml
@ -99,7 +104,6 @@ class ServerManagerApp:
if not isinstance(user_config, dict): if not isinstance(user_config, dict):
raise RuntimeError(f'Invalid config format; expected dict,' raise RuntimeError(f'Invalid config format; expected dict,'
f' got {type(user_config)}.') f' got {type(user_config)}.')
return config return config
def _get_binary_path(self) -> str: def _get_binary_path(self) -> str:
@ -256,6 +260,8 @@ class ServerManagerApp:
assert self._process is not None assert self._process is not None
# Send the initial server config which should kick things off. # Send the initial server config which should kick things off.
# (but make sure its values are still valid first)
dataclass_validate(self._config)
cmd = make_server_command(ServerCommand.CONFIG, self._config) cmd = make_server_command(ServerCommand.CONFIG, self._config)
assert self._process.stdin is not None assert self._process.stdin is not None
self._process.stdin.write(cmd) self._process.stdin.write(cmd)