mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 23:59:18 +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__/_outputter.cpython-38.opt-1.pyc",
|
||||||
"ba_data/python/efro/dataclassio/__pycache__/_pathcapture.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__/_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/_base.py",
|
||||||
"ba_data/python/efro/dataclassio/_inputter.py",
|
"ba_data/python/efro/dataclassio/_inputter.py",
|
||||||
"ba_data/python/efro/dataclassio/_outputter.py",
|
"ba_data/python/efro/dataclassio/_outputter.py",
|
||||||
"ba_data/python/efro/dataclassio/_pathcapture.py",
|
"ba_data/python/efro/dataclassio/_pathcapture.py",
|
||||||
"ba_data/python/efro/dataclassio/_prep.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/__init__.py",
|
||||||
"ba_data/python/efro/entity/__pycache__/__init__.cpython-38.opt-1.pyc",
|
"ba_data/python/efro/entity/__pycache__/__init__.cpython-38.opt-1.pyc",
|
||||||
"ba_data/python/efro/entity/__pycache__/_base.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/_outputter.py \
|
||||||
build/ba_data/python/efro/dataclassio/_pathcapture.py \
|
build/ba_data/python/efro/dataclassio/_pathcapture.py \
|
||||||
build/ba_data/python/efro/dataclassio/_prep.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/__init__.py \
|
||||||
build/ba_data/python/efro/entity/_base.py \
|
build/ba_data/python/efro/entity/_base.py \
|
||||||
build/ba_data/python/efro/entity/_entity.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__/_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__/_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__/_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__/__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__/_base.cpython-38.opt-1.pyc \
|
||||||
build/ba_data/python/efro/entity/__pycache__/_entity.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 -->
|
<!-- 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,
|
<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>
|
||||||
|
|||||||
@ -750,3 +750,22 @@ def test_datetime_limits() -> None:
|
|||||||
out['tval'][-1] += 1
|
out['tval'][-1] += 1
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dataclass_from_dict(_TestClass2, out)
|
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
|
# Type-checker currently allows this because both are Compounds
|
||||||
# but should fail at runtime since their subfield arrangement differs.
|
# but should fail at runtime since their subfield arrangement differs.
|
||||||
# reveal_type(ent1.grp.blah)
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ent2.grp = ent1.grp2
|
ent2.grp = ent1.grp2
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ from typing import TYPE_CHECKING, TypeVar
|
|||||||
from efro.dataclassio._outputter import _Outputter
|
from efro.dataclassio._outputter import _Outputter
|
||||||
from efro.dataclassio._inputter import _Inputter
|
from efro.dataclassio._inputter import _Inputter
|
||||||
from efro.dataclassio._base import Codec, IOAttrs
|
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
|
from efro.dataclassio._pathcapture import FieldStoragePathCapture
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Dict, Type, Tuple, Optional, List, Set
|
from typing import Any, Dict, Type, Tuple, Optional, List, Set
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Codec', 'IOAttrs', 'ioprepped', 'is_ioprepped_dataclass',
|
'Codec', 'IOAttrs', 'ioprep', 'ioprepped', 'is_ioprepped_dataclass',
|
||||||
'FieldStoragePathCapture', 'dataclass_to_dict', 'dataclass_to_json',
|
'FieldStoragePathCapture', 'dataclass_to_dict', 'dataclass_to_json',
|
||||||
'dataclass_from_dict', 'dataclass_from_json', 'dataclass_validate'
|
'dataclass_from_dict', 'dataclass_from_json', 'dataclass_validate'
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality for importing, exporting, and validating dataclasses.
|
"""Core components of dataclassio."""
|
||||||
|
|
||||||
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 __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality for importing, exporting, and validating dataclasses.
|
"""Functionality for dataclassio related to pulling data into 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Note: We do lots of comparing of exact types here which is normally
|
# Note: We do lots of comparing of exact types here which is normally
|
||||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality for importing, exporting, and validating dataclasses.
|
"""Functionality for dataclassio related to exporting data from 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Note: We do lots of comparing of exact types here which is normally
|
# Note: We do lots of comparing of exact types here which is normally
|
||||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality for importing, exporting, and validating dataclasses.
|
"""Functionality related to capturing nested dataclass paths."""
|
||||||
|
|
||||||
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 __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality for importing, exporting, and validating dataclasses.
|
"""Functionality for prepping types for use with dataclassio."""
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Note: We do lots of comparing of exact types here which is normally
|
# Note: We do lots of comparing of exact types here which is normally
|
||||||
# frowned upon (stuff like isinstance() is usually encouraged).
|
# frowned upon (stuff like isinstance() is usually encouraged).
|
||||||
@ -127,17 +121,20 @@ class PrepSession:
|
|||||||
' @efro.dataclassio.ioprepped decorator).', cls)
|
' @efro.dataclassio.ioprepped decorator).', cls)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# NOTE: perhaps we want to expose the globalns/localns args
|
# NOTE: Now passing the class' __dict__ (vars()) as locals
|
||||||
# to this?
|
# which allows us to pick up nested classes, etc.
|
||||||
# pylint: disable=unexpected-keyword-arg
|
# 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
|
# pylint: enable=unexpected-keyword-arg
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise RuntimeError(
|
print('GOT', cls.__dict__)
|
||||||
|
raise TypeError(
|
||||||
f'dataclassio prep for {cls} failed with error: {exc}.'
|
f'dataclassio prep for {cls} failed with error: {exc}.'
|
||||||
f' Make sure all types used in annotations are defined'
|
f' Make sure all types used in annotations are defined'
|
||||||
f' at the module level or add them as part of an explicit'
|
f' at the module or class level or add them as part of an'
|
||||||
f' prep call.') from exc
|
f' explicit prep call.') from exc
|
||||||
|
|
||||||
# noinspection PyDataclass
|
# noinspection PyDataclass
|
||||||
fields = dataclasses.fields(cls)
|
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