mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-09 01:09:40 +08:00
Wired up server config.yaml functionality
This commit is contained in:
parent
b42fc09ffc
commit
48f72ec123
14
.idea/dictionaries/ericf.xml
generated
14
.idea/dictionaries/ericf.xml
generated
@ -165,6 +165,7 @@
|
|||||||
<w>bgthread</w>
|
<w>bgthread</w>
|
||||||
<w>bhval</w>
|
<w>bhval</w>
|
||||||
<w>binc</w>
|
<w>binc</w>
|
||||||
|
<w>bincfg</w>
|
||||||
<w>bindcode</w>
|
<w>bindcode</w>
|
||||||
<w>bindvals</w>
|
<w>bindvals</w>
|
||||||
<w>bisectmodule</w>
|
<w>bisectmodule</w>
|
||||||
@ -512,6 +513,7 @@
|
|||||||
<w>efrotool</w>
|
<w>efrotool</w>
|
||||||
<w>efrotools</w>
|
<w>efrotools</w>
|
||||||
<w>eftools</w>
|
<w>eftools</w>
|
||||||
|
<w>efxjtp</w>
|
||||||
<w>eids</w>
|
<w>eids</w>
|
||||||
<w>elementtree</w>
|
<w>elementtree</w>
|
||||||
<w>elim</w>
|
<w>elim</w>
|
||||||
@ -588,6 +590,8 @@
|
|||||||
<w>fhashes</w>
|
<w>fhashes</w>
|
||||||
<w>fhdr</w>
|
<w>fhdr</w>
|
||||||
<w>fieldattr</w>
|
<w>fieldattr</w>
|
||||||
|
<w>fieldsdict</w>
|
||||||
|
<w>fieldtype</w>
|
||||||
<w>fieldtypes</w>
|
<w>fieldtypes</w>
|
||||||
<w>filebytes</w>
|
<w>filebytes</w>
|
||||||
<w>filecmp</w>
|
<w>filecmp</w>
|
||||||
@ -941,6 +945,7 @@
|
|||||||
<w>lazybuild</w>
|
<w>lazybuild</w>
|
||||||
<w>lazybuilddir</w>
|
<w>lazybuilddir</w>
|
||||||
<w>lbits</w>
|
<w>lbits</w>
|
||||||
|
<w>lbld</w>
|
||||||
<w>lcfg</w>
|
<w>lcfg</w>
|
||||||
<w>lcolor</w>
|
<w>lcolor</w>
|
||||||
<w>lcrypto</w>
|
<w>lcrypto</w>
|
||||||
@ -1208,13 +1213,16 @@
|
|||||||
<w>objs</w>
|
<w>objs</w>
|
||||||
<w>objt</w>
|
<w>objt</w>
|
||||||
<w>objtype</w>
|
<w>objtype</w>
|
||||||
|
<w>obval</w>
|
||||||
<w>occurrances</w>
|
<w>occurrances</w>
|
||||||
<w>oculus</w>
|
<w>oculus</w>
|
||||||
<w>offsanchor</w>
|
<w>offsanchor</w>
|
||||||
|
<w>ofval</w>
|
||||||
<w>oggenc</w>
|
<w>oggenc</w>
|
||||||
<w>oghash</w>
|
<w>oghash</w>
|
||||||
<w>oghashes</w>
|
<w>oghashes</w>
|
||||||
<w>ogval</w>
|
<w>ogval</w>
|
||||||
|
<w>oival</w>
|
||||||
<w>oldlady</w>
|
<w>oldlady</w>
|
||||||
<w>onln</w>
|
<w>onln</w>
|
||||||
<w>onscreencountdown</w>
|
<w>onscreencountdown</w>
|
||||||
@ -1234,6 +1242,7 @@
|
|||||||
<w>osascript</w>
|
<w>osascript</w>
|
||||||
<w>osmusic</w>
|
<w>osmusic</w>
|
||||||
<w>ostype</w>
|
<w>ostype</w>
|
||||||
|
<w>osval</w>
|
||||||
<w>otherplayer</w>
|
<w>otherplayer</w>
|
||||||
<w>otherspawn</w>
|
<w>otherspawn</w>
|
||||||
<w>ourself</w>
|
<w>ourself</w>
|
||||||
@ -1473,6 +1482,8 @@
|
|||||||
<w>representer</w>
|
<w>representer</w>
|
||||||
<w>reprlib</w>
|
<w>reprlib</w>
|
||||||
<w>reqs</w>
|
<w>reqs</w>
|
||||||
|
<w>reqtype</w>
|
||||||
|
<w>reqtypes</w>
|
||||||
<w>resample</w>
|
<w>resample</w>
|
||||||
<w>resourcetypeinfo</w>
|
<w>resourcetypeinfo</w>
|
||||||
<w>respawn</w>
|
<w>respawn</w>
|
||||||
@ -1573,6 +1584,7 @@
|
|||||||
<w>shiftposition</w>
|
<w>shiftposition</w>
|
||||||
<w>shouldn</w>
|
<w>shouldn</w>
|
||||||
<w>showpoints</w>
|
<w>showpoints</w>
|
||||||
|
<w>showstats</w>
|
||||||
<w>showsubseconds</w>
|
<w>showsubseconds</w>
|
||||||
<w>shroom</w>
|
<w>shroom</w>
|
||||||
<w>shutil</w>
|
<w>shutil</w>
|
||||||
@ -1720,6 +1732,7 @@
|
|||||||
<w>tbutton</w>
|
<w>tbutton</w>
|
||||||
<w>tcall</w>
|
<w>tcall</w>
|
||||||
<w>tchar</w>
|
<w>tchar</w>
|
||||||
|
<w>tclass</w>
|
||||||
<w>tcombine</w>
|
<w>tcombine</w>
|
||||||
<w>tdelay</w>
|
<w>tdelay</w>
|
||||||
<w>tdval</w>
|
<w>tdval</w>
|
||||||
@ -1833,6 +1846,7 @@
|
|||||||
<w>txtval</w>
|
<w>txtval</w>
|
||||||
<w>txtw</w>
|
<w>txtw</w>
|
||||||
<w>typeargs</w>
|
<w>typeargs</w>
|
||||||
|
<w>typecheck</w>
|
||||||
<w>typechecker</w>
|
<w>typechecker</w>
|
||||||
<w>typedval</w>
|
<w>typedval</w>
|
||||||
<w>typeshed</w>
|
<w>typeshed</w>
|
||||||
|
|||||||
6
Makefile
6
Makefile
@ -596,8 +596,12 @@ test-assetmanager:
|
|||||||
@tools/snippets pytest -o log_cli=true -o log_cli_level=debug -s -v \
|
@tools/snippets pytest -o log_cli=true -o log_cli_level=debug -s -v \
|
||||||
tests/test_ba/test_assetmanager.py::test_assetmanager
|
tests/test_ba/test_assetmanager.py::test_assetmanager
|
||||||
|
|
||||||
|
test-dataclassutils:
|
||||||
|
@tools/snippets pytest -o log_cli=true -o log_cli_level=debug -s -v \
|
||||||
|
tests/test_efro/test_dataclassutils.py
|
||||||
|
|
||||||
# Tell make which of these targets don't represent files.
|
# Tell make which of these targets don't represent files.
|
||||||
.PHONY: test test-full
|
.PHONY: test test-full test-assetmanager
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@ -453,9 +453,11 @@
|
|||||||
"ba_data/python/bastd/ui/watch.py",
|
"ba_data/python/bastd/ui/watch.py",
|
||||||
"ba_data/python/efro/__init__.py",
|
"ba_data/python/efro/__init__.py",
|
||||||
"ba_data/python/efro/__pycache__/__init__.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/__pycache__/__init__.cpython-37.opt-1.pyc",
|
||||||
|
"ba_data/python/efro/__pycache__/dataclassutils.cpython-37.opt-1.pyc",
|
||||||
"ba_data/python/efro/__pycache__/executils.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/__pycache__/executils.cpython-37.opt-1.pyc",
|
||||||
"ba_data/python/efro/__pycache__/jsonutils.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/__pycache__/jsonutils.cpython-37.opt-1.pyc",
|
||||||
"ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc",
|
||||||
|
"ba_data/python/efro/dataclassutils.py",
|
||||||
"ba_data/python/efro/entity/__init__.py",
|
"ba_data/python/efro/entity/__init__.py",
|
||||||
"ba_data/python/efro/entity/__pycache__/__init__.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/entity/__pycache__/__init__.cpython-37.opt-1.pyc",
|
||||||
"ba_data/python/efro/entity/__pycache__/_base.cpython-37.opt-1.pyc",
|
"ba_data/python/efro/entity/__pycache__/_base.cpython-37.opt-1.pyc",
|
||||||
|
|||||||
@ -144,6 +144,7 @@ ASSET_TARGETS_WIN_X64 += $(EXTRAS_TARGETS_WIN_X64)
|
|||||||
SCRIPT_TARGETS_PY_1 = \
|
SCRIPT_TARGETS_PY_1 = \
|
||||||
build/server/ballisticacore_server.py \
|
build/server/ballisticacore_server.py \
|
||||||
build/ba_data/python/efro/executils.py \
|
build/ba_data/python/efro/executils.py \
|
||||||
|
build/ba_data/python/efro/dataclassutils.py \
|
||||||
build/ba_data/python/efro/util.py \
|
build/ba_data/python/efro/util.py \
|
||||||
build/ba_data/python/efro/__init__.py \
|
build/ba_data/python/efro/__init__.py \
|
||||||
build/ba_data/python/efro/jsonutils.py \
|
build/ba_data/python/efro/jsonutils.py \
|
||||||
@ -384,6 +385,7 @@ SCRIPT_TARGETS_PY_1 = \
|
|||||||
SCRIPT_TARGETS_PYC_1 = \
|
SCRIPT_TARGETS_PYC_1 = \
|
||||||
build/server/__pycache__/ballisticacore_server.cpython-37.opt-1.pyc \
|
build/server/__pycache__/ballisticacore_server.cpython-37.opt-1.pyc \
|
||||||
build/ba_data/python/efro/__pycache__/executils.cpython-37.opt-1.pyc \
|
build/ba_data/python/efro/__pycache__/executils.cpython-37.opt-1.pyc \
|
||||||
|
build/ba_data/python/efro/__pycache__/dataclassutils.cpython-37.opt-1.pyc \
|
||||||
build/ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc \
|
build/ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc \
|
||||||
build/ba_data/python/efro/__pycache__/__init__.cpython-37.opt-1.pyc \
|
build/ba_data/python/efro/__pycache__/__init__.cpython-37.opt-1.pyc \
|
||||||
build/ba_data/python/efro/__pycache__/jsonutils.cpython-37.opt-1.pyc \
|
build/ba_data/python/efro/__pycache__/jsonutils.cpython-37.opt-1.pyc \
|
||||||
@ -644,6 +646,11 @@ build/ba_data/python/efro/__pycache__/executils.cpython-37.opt-1.pyc: \
|
|||||||
@echo Compiling script: $^
|
@echo Compiling script: $^
|
||||||
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
|
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
|
||||||
|
|
||||||
|
build/ba_data/python/efro/__pycache__/dataclassutils.cpython-37.opt-1.pyc: \
|
||||||
|
build/ba_data/python/efro/dataclassutils.py
|
||||||
|
@echo Compiling script: $^
|
||||||
|
@rm -rf $@ && $(TOOLS_DIR)/snippets compile_python_files $^ && chmod 444 $@
|
||||||
|
|
||||||
build/ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc: \
|
build/ba_data/python/efro/__pycache__/util.cpython-37.opt-1.pyc: \
|
||||||
build/ba_data/python/efro/util.py
|
build/ba_data/python/efro/util.py
|
||||||
@echo Compiling script: $^
|
@echo Compiling script: $^
|
||||||
|
|||||||
@ -104,8 +104,8 @@ class ServerConfig:
|
|||||||
# this to provide a convenient in-game link to it in the server-browser
|
# this to provide a convenient in-game link to it in the server-browser
|
||||||
# beside the server name.
|
# beside the server name.
|
||||||
# if ${ACCOUNT} is present in the string, it will be replaced by the
|
# if ${ACCOUNT} is present in the string, it will be replaced by the
|
||||||
# currently-signed-in account's id. To get info about an account,
|
# currently-signed-in account's id. To fetch info about an account,
|
||||||
# you can use the following url:
|
# your backend server can use the following url:
|
||||||
# http://bombsquadgame.com/accountquery?id=ACCOUNT_ID_HERE
|
# http://bombsquadgame.com/accountquery?id=ACCOUNT_ID_HERE
|
||||||
stats_url: Optional[str] = None
|
stats_url: Optional[str] = None
|
||||||
|
|
||||||
|
|||||||
116
assets/src/ba_data/python/efro/dataclassutils.py
Normal file
116
assets/src/ba_data/python/efro/dataclassutils.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# Copyright (c) 2011-2020 Eric Froemling
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
"""Custom functionality for dealing with dataclasses."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any, Dict, Type, Tuple
|
||||||
|
|
||||||
|
# For fields with these type strings, we require a passed value's type
|
||||||
|
# to exactly match one of the tuple values to consider the assignment valid.
|
||||||
|
_ASSIGN_TYPES: Dict[str, Tuple[Type, ...]] = {
|
||||||
|
'bool': (bool, ),
|
||||||
|
'str': (str, ),
|
||||||
|
'int': (int, ),
|
||||||
|
'float': (float, ),
|
||||||
|
'Optional[int]': (int, type(None)),
|
||||||
|
'Optional[str]': (str, type(None)),
|
||||||
|
'Optional[bool]': (bool, type(None)),
|
||||||
|
'Optional[float]': (float, type(None)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def dataclass_assign(instance: Any, values: Dict[str, Any]) -> None:
|
||||||
|
"""Safely assign values from a dict to a dataclass instance.
|
||||||
|
|
||||||
|
A TypeError will be raised if types to not match the dataclass fields
|
||||||
|
or are unsupported by this function. Note that a limited number of
|
||||||
|
types are supported. More can be added as needed.
|
||||||
|
|
||||||
|
Exact types are strictly checked, so a bool cannot be passed for
|
||||||
|
an int field, an int can't be passed for a float, etc.
|
||||||
|
(can reexamine this strictness if it proves to be a problem)
|
||||||
|
|
||||||
|
An AttributeError will be raised if attributes are passed which are
|
||||||
|
not present on the dataclass as fields.
|
||||||
|
|
||||||
|
This function may be significantly slower than simply passing dict
|
||||||
|
values to a dataclass' constructor or other more direct methods, but
|
||||||
|
the increased safety checks may be worth the extra overhead in some
|
||||||
|
cases.
|
||||||
|
"""
|
||||||
|
if not dataclasses.is_dataclass(instance):
|
||||||
|
raise TypeError(f'Passed instance {instance} is not a dataclass.')
|
||||||
|
if not isinstance(values, dict):
|
||||||
|
raise TypeError("Expected a dict for 'values' arg.")
|
||||||
|
fields = dataclasses.fields(instance)
|
||||||
|
fieldsdict = {f.name: f for f in fields}
|
||||||
|
for key, value in values.items():
|
||||||
|
if key not in fieldsdict:
|
||||||
|
raise AttributeError(f"'{type(instance).__name__}' dataclass has"
|
||||||
|
f" no '{key}' field.")
|
||||||
|
field = fieldsdict[key]
|
||||||
|
|
||||||
|
# We expect to be operating with 'from __future__ import annotations'
|
||||||
|
# so this should always be a string for us; not an actual type.
|
||||||
|
# Complain if we come across an actual type.
|
||||||
|
fieldtype: str = field.type # type: ignore
|
||||||
|
if not isinstance(fieldtype, str):
|
||||||
|
raise RuntimeError(
|
||||||
|
f'Dataclass {type(instance).__name__} seems to have'
|
||||||
|
f' been created without "from __future__ import annotations";'
|
||||||
|
f' those dataclasses are unsupported here.')
|
||||||
|
|
||||||
|
reqtypes = _ASSIGN_TYPES.get(fieldtype)
|
||||||
|
if reqtypes is not None:
|
||||||
|
# pylint: disable=unidiomatic-typecheck
|
||||||
|
if not any(type(value) is t for t in reqtypes):
|
||||||
|
# if not isinstance(value, reqtype):
|
||||||
|
if len(reqtypes) == 1:
|
||||||
|
expected = reqtypes[0].__name__
|
||||||
|
else:
|
||||||
|
names = ', '.join(t.__name__ for t in reqtypes)
|
||||||
|
expected = f'Union[{names}]'
|
||||||
|
raise TypeError(f'Invalid value type for "{key}";'
|
||||||
|
f' expected "{expected}", got'
|
||||||
|
f' "{type(value).__name__}".')
|
||||||
|
else:
|
||||||
|
raise TypeError(f'Field type "{fieldtype}" is unsupported here.')
|
||||||
|
|
||||||
|
# Ok, if we made it here, the value is kosher. Do the assign.
|
||||||
|
setattr(instance, key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def dataclass_validate(instance: Any) -> None:
|
||||||
|
"""Ensure values in a dataclass are correct types.
|
||||||
|
|
||||||
|
Note that this will always fail if a dataclass has value types
|
||||||
|
unsupported by this module.
|
||||||
|
"""
|
||||||
|
# We currently simply operate by grabbing dataclass values as a dict
|
||||||
|
# and passing them through dataclass_assign().
|
||||||
|
# It would be possible to write slightly more efficient custom code,
|
||||||
|
# this, but this keeps things simple and will allow us to easily
|
||||||
|
# incorporate things like value coercion later if we add that.
|
||||||
|
dataclass_assign(instance, dataclasses.asdict(instance))
|
||||||
@ -30,8 +30,8 @@ if TYPE_CHECKING:
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# Special attr we included for our extended type information
|
# Special attr we included for our extended type information
|
||||||
# (extended-json-type)
|
# (efro-extended-json-type)
|
||||||
TYPE_TAG = '_xjtp'
|
TYPE_TAG = '_efxjtp'
|
||||||
|
|
||||||
_pytz_utc: Any
|
_pytz_utc: Any
|
||||||
|
|
||||||
|
|||||||
@ -31,8 +31,8 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
# We make use of the bacommon package and site-packages included
|
# We make use of the bacommon and efro packages as well as site-packages
|
||||||
# with our bundled Ballistica dist.
|
# included with our bundled Ballistica dist.
|
||||||
sys.path += [
|
sys.path += [
|
||||||
str(Path(os.getcwd(), 'dist', 'ba_data', 'python')),
|
str(Path(os.getcwd(), 'dist', 'ba_data', 'python')),
|
||||||
str(Path(os.getcwd(), 'dist', 'ba_data', 'python-site-packages'))
|
str(Path(os.getcwd(), 'dist', 'ba_data', 'python-site-packages'))
|
||||||
@ -40,6 +40,7 @@ sys.path += [
|
|||||||
|
|
||||||
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
|
||||||
@ -55,13 +56,14 @@ class ServerManagerApp:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
||||||
# We actually run from the 'dist' subdir.
|
self._config = self._load_config()
|
||||||
|
|
||||||
|
# We actually operate from the 'dist' subdir.
|
||||||
if not os.path.isdir('dist'):
|
if not os.path.isdir('dist'):
|
||||||
raise RuntimeError('"dist" directory not found.')
|
raise RuntimeError('"dist" directory not found.')
|
||||||
os.chdir('dist')
|
os.chdir('dist')
|
||||||
|
|
||||||
self._binary_path = self._get_binary_path()
|
self._binary_path = self._get_binary_path()
|
||||||
self._config = ServerConfig()
|
|
||||||
|
|
||||||
self._binary_commands: List[str] = []
|
self._binary_commands: List[str] = []
|
||||||
self._binary_commands_lock = threading.Lock()
|
self._binary_commands_lock = threading.Lock()
|
||||||
@ -75,6 +77,31 @@ class ServerManagerApp:
|
|||||||
self._process: Optional[subprocess.Popen[bytes]] = None
|
self._process: Optional[subprocess.Popen[bytes]] = None
|
||||||
self._process_launch_time: Optional[float] = None
|
self._process_launch_time: Optional[float] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self) -> ServerConfig:
|
||||||
|
"""The current config settings for the app."""
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def _load_config(self) -> ServerConfig:
|
||||||
|
user_config_path = 'config.yaml'
|
||||||
|
|
||||||
|
# Start with a default config, and if there is a config.yaml,
|
||||||
|
# override parts of it.
|
||||||
|
config = ServerConfig()
|
||||||
|
if os.path.exists(user_config_path):
|
||||||
|
import yaml
|
||||||
|
with open(user_config_path) as infile:
|
||||||
|
user_config = yaml.safe_load(infile.read())
|
||||||
|
dataclass_assign(config, user_config)
|
||||||
|
|
||||||
|
# An empty config file will yield None, and that's ok.
|
||||||
|
if user_config is not None:
|
||||||
|
if not isinstance(user_config, dict):
|
||||||
|
raise RuntimeError(f'Invalid config format; expected dict,'
|
||||||
|
f' got {type(user_config)}.')
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def _get_binary_path(self) -> str:
|
def _get_binary_path(self) -> str:
|
||||||
"""Locate the game binary that we'll use."""
|
"""Locate the game binary that we'll use."""
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
@ -215,15 +242,15 @@ class ServerManagerApp:
|
|||||||
os.makedirs('ba_root', exist_ok=True)
|
os.makedirs('ba_root', exist_ok=True)
|
||||||
if os.path.exists('ba_root/config.json'):
|
if os.path.exists('ba_root/config.json'):
|
||||||
with open('ba_root/config.json') as infile:
|
with open('ba_root/config.json') as infile:
|
||||||
bacfg = json.loads(infile.read())
|
bincfg = json.loads(infile.read())
|
||||||
else:
|
else:
|
||||||
bacfg = {}
|
bincfg = {}
|
||||||
bacfg['Port'] = self._config.port
|
bincfg['Port'] = self._config.port
|
||||||
bacfg['Enable Telnet'] = self._config.enable_telnet
|
bincfg['Enable Telnet'] = self._config.enable_telnet
|
||||||
bacfg['Telnet Port'] = self._config.telnet_port
|
bincfg['Telnet Port'] = self._config.telnet_port
|
||||||
bacfg['Telnet Password'] = self._config.telnet_password
|
bincfg['Telnet Password'] = self._config.telnet_password
|
||||||
with open('ba_root/config.json', 'w') as outfile:
|
with open('ba_root/config.json', 'w') as outfile:
|
||||||
outfile.write(json.dumps(bacfg))
|
outfile.write(json.dumps(bincfg))
|
||||||
|
|
||||||
def _run_process_until_exit(self) -> None:
|
def _run_process_until_exit(self) -> None:
|
||||||
assert self._process is not None
|
assert self._process is not None
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||||
<h4><em>last updated on 2020-04-29 for Ballistica version 1.5.0 build 20001</em></h4>
|
<h4><em>last updated on 2020-04-30 for Ballistica version 1.5.0 build 20001</em></h4>
|
||||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
155
tests/test_efro/test_dataclassutils.py
Normal file
155
tests/test_efro/test_dataclassutils.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# Copyright (c) 2011-2020 Eric Froemling
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
"""Testing dataclassutils functionality."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from efro.dataclassutils import dataclass_assign, dataclass_validate
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def test_assign() -> None:
|
||||||
|
"""Testing various assignments."""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _TestClass:
|
||||||
|
ival: int = 0
|
||||||
|
sval: str = ''
|
||||||
|
bval: bool = True
|
||||||
|
fval: float = 1.0
|
||||||
|
oival: Optional[int] = None
|
||||||
|
osval: Optional[str] = None
|
||||||
|
obval: Optional[bool] = None
|
||||||
|
ofval: Optional[float] = None
|
||||||
|
|
||||||
|
tclass = _TestClass()
|
||||||
|
|
||||||
|
class _TestClass2:
|
||||||
|
pass
|
||||||
|
|
||||||
|
tclass2 = _TestClass2()
|
||||||
|
|
||||||
|
# Arg types:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass2, {})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, []) # type: ignore
|
||||||
|
|
||||||
|
# Invalid attrs.
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
dataclass_assign(tclass, {'nonexistent': 'foo'})
|
||||||
|
|
||||||
|
# Correct types.
|
||||||
|
dataclass_assign(tclass, {
|
||||||
|
'ival': 1,
|
||||||
|
'sval': 'foo',
|
||||||
|
'bval': True,
|
||||||
|
'fval': 2.0,
|
||||||
|
})
|
||||||
|
dataclass_assign(tclass, {
|
||||||
|
'oival': None,
|
||||||
|
'osval': None,
|
||||||
|
'obval': None,
|
||||||
|
'ofval': None,
|
||||||
|
})
|
||||||
|
dataclass_assign(tclass, {
|
||||||
|
'oival': 1,
|
||||||
|
'osval': 'foo',
|
||||||
|
'obval': True,
|
||||||
|
'ofval': 2.0,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Type mismatches.
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'ival': 'foo'})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'sval': 1})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'bval': 2})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'oival': 'foo'})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'osval': 1})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'obval': 2})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'ofval': 'blah'})
|
||||||
|
|
||||||
|
# More subtle ones (we currently require EXACT type matches)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'ival': True})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'fval': 2})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'bval': 1})
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_assign(tclass, {'ofval': 1})
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate() -> None:
|
||||||
|
"""Testing validation."""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _TestClass:
|
||||||
|
ival: int = 0
|
||||||
|
sval: str = ''
|
||||||
|
bval: bool = True
|
||||||
|
fval: float = 1.0
|
||||||
|
oival: Optional[int] = None
|
||||||
|
osval: Optional[str] = None
|
||||||
|
obval: Optional[bool] = None
|
||||||
|
ofval: Optional[float] = None
|
||||||
|
|
||||||
|
# Should pass by default.
|
||||||
|
tclass = _TestClass()
|
||||||
|
dataclass_validate(tclass)
|
||||||
|
|
||||||
|
# No longer valid.
|
||||||
|
tclass.fval = 1
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_validate(tclass)
|
||||||
|
|
||||||
|
# Should pass by default.
|
||||||
|
tclass = _TestClass()
|
||||||
|
dataclass_validate(tclass)
|
||||||
|
|
||||||
|
# No longer valid.
|
||||||
|
# noinspection PyTypeHints
|
||||||
|
tclass.ival = None # type: ignore
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_validate(tclass)
|
||||||
@ -615,11 +615,17 @@ def _get_server_config_template_yaml() -> str:
|
|||||||
assert veq == '='
|
assert veq == '='
|
||||||
vval = eval(vval_raw) # pylint: disable=eval-used
|
vval = eval(vval_raw) # pylint: disable=eval-used
|
||||||
|
|
||||||
# Override a few specifics:
|
# Filter/override a few things.
|
||||||
if vname == 'playlist_code':
|
if vname == 'playlist_code':
|
||||||
|
# User wouldn't want to pass the default of None here.
|
||||||
vval = 12345
|
vval = 12345
|
||||||
|
elif vname == 'stats_url':
|
||||||
|
vval = ('https://mystatssite.com/'
|
||||||
|
'showstats?player=${ACCOUNT}')
|
||||||
lines_out.append('#' + yaml.dump({vname: vval}).strip())
|
lines_out.append('#' + yaml.dump({vname: vval}).strip())
|
||||||
else:
|
else:
|
||||||
|
# Convert comments referring to python bools to yaml bools.
|
||||||
|
line = line.replace('True', 'true').replace('False', 'false')
|
||||||
lines_out.append(line)
|
lines_out.append(line)
|
||||||
return '\n'.join(lines_out)
|
return '\n'.join(lines_out)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user