mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-28 10:03:15 +08:00
dataclassio now supports nested types
This commit is contained in:
parent
329e254246
commit
82515efbc5
@ -510,11 +510,13 @@
|
||||
"ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.opt-1.pyc",
|
||||
"ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.opt-1.pyc",
|
||||
"ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.opt-1.pyc",
|
||||
"ba_data/python/efro/dataclassio/__pycache__/extras.cpython-38.opt-1.pyc",
|
||||
"ba_data/python/efro/dataclassio/_base.py",
|
||||
"ba_data/python/efro/dataclassio/_inputter.py",
|
||||
"ba_data/python/efro/dataclassio/_outputter.py",
|
||||
"ba_data/python/efro/dataclassio/_pathcapture.py",
|
||||
"ba_data/python/efro/dataclassio/_prep.py",
|
||||
"ba_data/python/efro/dataclassio/extras.py",
|
||||
"ba_data/python/efro/entity/__init__.py",
|
||||
"ba_data/python/efro/entity/__pycache__/__init__.cpython-38.opt-1.pyc",
|
||||
"ba_data/python/efro/entity/__pycache__/_base.cpython-38.opt-1.pyc",
|
||||
|
||||
@ -649,6 +649,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
|
||||
build/ba_data/python/efro/dataclassio/_outputter.py \
|
||||
build/ba_data/python/efro/dataclassio/_pathcapture.py \
|
||||
build/ba_data/python/efro/dataclassio/_prep.py \
|
||||
build/ba_data/python/efro/dataclassio/extras.py \
|
||||
build/ba_data/python/efro/entity/__init__.py \
|
||||
build/ba_data/python/efro/entity/_base.py \
|
||||
build/ba_data/python/efro/entity/_entity.py \
|
||||
@ -676,6 +677,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
|
||||
build/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/entity/__pycache__/__init__.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/entity/__pycache__/_base.cpython-38.opt-1.pyc \
|
||||
build/ba_data/python/efro/entity/__pycache__/_entity.cpython-38.opt-1.pyc \
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2021-09-28 for Ballistica version 1.6.5 build 20393</em></h4>
|
||||
<h4><em>last updated on 2021-09-29 for Ballistica version 1.6.5 build 20393</em></h4>
|
||||
<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>
|
||||
<hr>
|
||||
|
||||
@ -750,3 +750,22 @@ def test_datetime_limits() -> None:
|
||||
out['tval'][-1] += 1
|
||||
with pytest.raises(ValueError):
|
||||
dataclass_from_dict(_TestClass2, out)
|
||||
|
||||
|
||||
def test_nested() -> None:
|
||||
"""Test nesting dataclasses."""
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass:
|
||||
|
||||
class _TestEnum(Enum):
|
||||
VAL1 = 'val1'
|
||||
VAL2 = 'val2'
|
||||
|
||||
@dataclass
|
||||
class _TestSubClass:
|
||||
ival: int = 0
|
||||
|
||||
subval: _TestSubClass = field(default_factory=_TestSubClass)
|
||||
enval: _TestEnum = _TestEnum.VAL1
|
||||
|
||||
@ -263,7 +263,6 @@ def test_field_copies() -> None:
|
||||
|
||||
# Type-checker currently allows this because both are Compounds
|
||||
# but should fail at runtime since their subfield arrangement differs.
|
||||
# reveal_type(ent1.grp.blah)
|
||||
with pytest.raises(ValueError):
|
||||
ent2.grp = ent1.grp2
|
||||
|
||||
|
||||
@ -15,14 +15,14 @@ from typing import TYPE_CHECKING, TypeVar
|
||||
from efro.dataclassio._outputter import _Outputter
|
||||
from efro.dataclassio._inputter import _Inputter
|
||||
from efro.dataclassio._base import Codec, IOAttrs
|
||||
from efro.dataclassio._prep import ioprepped, is_ioprepped_dataclass
|
||||
from efro.dataclassio._prep import ioprep, ioprepped, is_ioprepped_dataclass
|
||||
from efro.dataclassio._pathcapture import FieldStoragePathCapture
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Dict, Type, Tuple, Optional, List, Set
|
||||
|
||||
__all__ = [
|
||||
'Codec', 'IOAttrs', 'ioprepped', 'is_ioprepped_dataclass',
|
||||
'Codec', 'IOAttrs', 'ioprep', 'ioprepped', 'is_ioprepped_dataclass',
|
||||
'FieldStoragePathCapture', 'dataclass_to_dict', 'dataclass_to_json',
|
||||
'dataclass_from_dict', 'dataclass_from_json', 'dataclass_validate'
|
||||
]
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
# 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.
|
||||
"""
|
||||
"""Core components of dataclassio."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
# 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.
|
||||
"""
|
||||
"""Functionality for dataclassio related to pulling data into dataclasses."""
|
||||
|
||||
# Note: We do lots of comparing of exact types here which is normally
|
||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
# 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.
|
||||
"""
|
||||
"""Functionality for dataclassio related to exporting data from dataclasses."""
|
||||
|
||||
# Note: We do lots of comparing of exact types here which is normally
|
||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
# 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.
|
||||
"""
|
||||
"""Functionality related to capturing nested dataclass paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
# 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.
|
||||
"""
|
||||
"""Functionality for prepping types for use with dataclassio."""
|
||||
|
||||
# Note: We do lots of comparing of exact types here which is normally
|
||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||
@ -127,17 +121,20 @@ class PrepSession:
|
||||
' @efro.dataclassio.ioprepped decorator).', cls)
|
||||
|
||||
try:
|
||||
# NOTE: perhaps we want to expose the globalns/localns args
|
||||
# to this?
|
||||
# NOTE: Now passing the class' __dict__ (vars()) as locals
|
||||
# which allows us to pick up nested classes, etc.
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
resolved_annotations = get_type_hints(cls, include_extras=True)
|
||||
resolved_annotations = get_type_hints(cls,
|
||||
localns=vars(cls),
|
||||
include_extras=True)
|
||||
# pylint: enable=unexpected-keyword-arg
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
print('GOT', cls.__dict__)
|
||||
raise TypeError(
|
||||
f'dataclassio prep for {cls} failed with error: {exc}.'
|
||||
f' Make sure all types used in annotations are defined'
|
||||
f' at the module level or add them as part of an explicit'
|
||||
f' prep call.') from exc
|
||||
f' at the module or class level or add them as part of an'
|
||||
f' explicit prep call.') from exc
|
||||
|
||||
# noinspection PyDataclass
|
||||
fields = dataclasses.fields(cls)
|
||||
|
||||
66
tools/efro/dataclassio/extras.py
Normal file
66
tools/efro/dataclassio/extras.py
Normal file
@ -0,0 +1,66 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Extra rarely-needed functionality related to dataclasses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Dict, Type, Tuple, Optional, List, Set
|
||||
|
||||
|
||||
def dataclass_diff(obj1: Any, obj2: Any) -> str:
|
||||
"""Generate a string showing differences between two dataclass instances.
|
||||
|
||||
Both must be of the exact same type.
|
||||
"""
|
||||
diff = _diff(obj1, obj2, 2)
|
||||
return ' <no differences>' if diff == '' else diff
|
||||
|
||||
|
||||
class DataclassDiff:
|
||||
"""Wraps dataclass_diff() in an object for efficiency.
|
||||
|
||||
It is preferable to pass this to logging calls instead of the
|
||||
final diff string since the diff will never be generated if
|
||||
the associated logging level is not being emitted.
|
||||
"""
|
||||
|
||||
def __init__(self, obj1: Any, obj2: Any):
|
||||
self._obj1 = obj1
|
||||
self._obj2 = obj2
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return dataclass_diff(self._obj1, self._obj2)
|
||||
|
||||
|
||||
def _diff(obj1: Any, obj2: Any, indent: int) -> str:
|
||||
assert dataclasses.is_dataclass(obj1)
|
||||
assert dataclasses.is_dataclass(obj2)
|
||||
if type(obj1) is not type(obj2):
|
||||
raise TypeError(f'Passed objects are not of the same'
|
||||
f' type ({type(obj1)} and {type(obj2)}).')
|
||||
bits: List[str] = []
|
||||
indentstr = ' ' * indent
|
||||
fields = dataclasses.fields(obj1)
|
||||
for field in fields:
|
||||
fieldname = field.name
|
||||
val1 = getattr(obj1, fieldname)
|
||||
val2 = getattr(obj2, fieldname)
|
||||
|
||||
# For nested dataclasses, dive in and do nice piecewise compares.
|
||||
if (dataclasses.is_dataclass(val1) and dataclasses.is_dataclass(val2)
|
||||
and type(val1) is type(val2)):
|
||||
diff = _diff(val1, val2, indent + 2)
|
||||
if diff != '':
|
||||
bits.append(f'{indentstr}{fieldname}:')
|
||||
bits.append(diff)
|
||||
|
||||
# For all else just do a single line
|
||||
# (perhaps we could improve on this for other complex types)
|
||||
else:
|
||||
if val1 != val2:
|
||||
bits.append(f'{indentstr}{fieldname}: {val1} -> {val2}')
|
||||
return '\n'.join(bits)
|
||||
Loading…
x
Reference in New Issue
Block a user