mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
# Released under the MIT License. See LICENSE for details.
|
|
#
|
|
"""Functionality for importing, exporting, and validating dataclasses.
|
|
|
|
This allows complex nested dataclasses to be flattened to json-compatible
|
|
data and restored from said data. It also gracefully handles and preserves
|
|
unrecognized attribute data, allowing older clients to interact with newer
|
|
data formats in a nondestructive manner.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import TYPE_CHECKING, TypeVar
|
|
|
|
from efro.dataclassio._outputter import _Outputter
|
|
from efro.dataclassio._inputter import _Inputter
|
|
from efro.dataclassio._base import Codec
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any
|
|
|
|
T = TypeVar('T')
|
|
|
|
|
|
class JsonStyle(Enum):
|
|
"""Different style types for json."""
|
|
|
|
# Single line, no spaces, no sorting. Not deterministic.
|
|
# Use this for most storage purposes.
|
|
FAST = 'fast'
|
|
|
|
# Single line, no spaces, sorted keys. Deterministic.
|
|
# Use this when output may be hashed or compared for equality.
|
|
SORTED = 'sorted'
|
|
|
|
# Multiple lines, spaces, sorted keys. Deterministic.
|
|
# Use this for pretty human readable output.
|
|
PRETTY = 'pretty'
|
|
|
|
|
|
def dataclass_to_dict(
|
|
obj: Any, codec: Codec = Codec.JSON, coerce_to_float: bool = True
|
|
) -> dict:
|
|
"""Given a dataclass object, return a json-friendly dict.
|
|
|
|
All values will be checked to ensure they match the types specified
|
|
on fields. Note that a limited set of types and data configurations is
|
|
supported.
|
|
|
|
Values with type Any will be checked to ensure they match types supported
|
|
directly by json. This does not include types such as tuples which are
|
|
implicitly translated by Python's json module (as this would break
|
|
the ability to do a lossless round-trip with data).
|
|
|
|
If coerce_to_float is True, integer values present on float typed fields
|
|
will be converted to float in the dict output. If False, a TypeError
|
|
will be triggered.
|
|
"""
|
|
|
|
out = _Outputter(
|
|
obj, create=True, codec=codec, coerce_to_float=coerce_to_float
|
|
).run()
|
|
assert isinstance(out, dict)
|
|
return out
|
|
|
|
|
|
def dataclass_to_json(
|
|
obj: Any,
|
|
coerce_to_float: bool = True,
|
|
pretty: bool = False,
|
|
sort_keys: bool | None = None,
|
|
) -> str:
|
|
"""Utility function; return a json string from a dataclass instance.
|
|
|
|
Basically json.dumps(dataclass_to_dict(...)).
|
|
By default, keys are sorted for pretty output and not otherwise, but
|
|
this can be overridden by supplying a value for the 'sort_keys' arg.
|
|
"""
|
|
import json
|
|
|
|
jdict = dataclass_to_dict(
|
|
obj=obj, coerce_to_float=coerce_to_float, codec=Codec.JSON
|
|
)
|
|
if sort_keys is None:
|
|
sort_keys = pretty
|
|
if pretty:
|
|
return json.dumps(jdict, indent=2, sort_keys=sort_keys)
|
|
return json.dumps(jdict, separators=(',', ':'), sort_keys=sort_keys)
|
|
|
|
|
|
def dataclass_from_dict(
|
|
cls: type[T],
|
|
values: dict,
|
|
codec: Codec = Codec.JSON,
|
|
coerce_to_float: bool = True,
|
|
allow_unknown_attrs: bool = True,
|
|
discard_unknown_attrs: bool = False,
|
|
) -> T:
|
|
"""Given a dict, return a dataclass of a given type.
|
|
|
|
The dict must be formatted to match the specified codec (generally
|
|
json-friendly object types). This means that sequence values such as
|
|
tuples or sets should be passed as lists, enums should be passed as their
|
|
associated values, nested dataclasses should be passed as dicts, etc.
|
|
|
|
All values are checked to ensure their types/values are valid.
|
|
|
|
Data for attributes of type Any will be checked to ensure they match
|
|
types supported directly by json. This does not include types such
|
|
as tuples which are implicitly translated by Python's json module
|
|
(as this would break the ability to do a lossless round-trip with data).
|
|
|
|
If coerce_to_float is True, int values passed for float typed fields
|
|
will be converted to float values. Otherwise, a TypeError is raised.
|
|
|
|
If allow_unknown_attrs is False, AttributeErrors will be raised for
|
|
attributes present in the dict but not on the data class. Otherwise, they
|
|
will be preserved as part of the instance and included if it is
|
|
exported back to a dict, unless discard_unknown_attrs is True, in which
|
|
case they will simply be discarded.
|
|
"""
|
|
return _Inputter(
|
|
cls,
|
|
codec=codec,
|
|
coerce_to_float=coerce_to_float,
|
|
allow_unknown_attrs=allow_unknown_attrs,
|
|
discard_unknown_attrs=discard_unknown_attrs,
|
|
).run(values)
|
|
|
|
|
|
def dataclass_from_json(
|
|
cls: type[T],
|
|
json_str: str,
|
|
coerce_to_float: bool = True,
|
|
allow_unknown_attrs: bool = True,
|
|
discard_unknown_attrs: bool = False,
|
|
) -> T:
|
|
"""Utility function; return a dataclass instance given a json string.
|
|
|
|
Basically dataclass_from_dict(json.loads(...))
|
|
"""
|
|
import json
|
|
|
|
return dataclass_from_dict(
|
|
cls=cls,
|
|
values=json.loads(json_str),
|
|
coerce_to_float=coerce_to_float,
|
|
allow_unknown_attrs=allow_unknown_attrs,
|
|
discard_unknown_attrs=discard_unknown_attrs,
|
|
)
|
|
|
|
|
|
def dataclass_validate(
|
|
obj: Any, coerce_to_float: bool = True, codec: Codec = Codec.JSON
|
|
) -> None:
|
|
"""Ensure that values in a dataclass instance are the correct types."""
|
|
|
|
# Simply run an output pass but tell it not to generate data;
|
|
# only run validation.
|
|
_Outputter(
|
|
obj, create=False, codec=codec, coerce_to_float=coerce_to_float
|
|
).run()
|