mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-03 05:53:15 +08:00
Added tuple and datetime support to dataclassio
This commit is contained in:
parent
34592c16df
commit
c10cf5c2fe
@ -3940,18 +3940,18 @@
|
|||||||
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/8b/b8f8a75b3ded113231265f61da9d",
|
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/8b/b8f8a75b3ded113231265f61da9d",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/14/4e/bd10863753f44c7612ef697c4693",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/14/4e/bd10863753f44c7612ef697c4693",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/a0/883c84cb130780bb8bc8a2185604",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/a0/883c84cb130780bb8bc8a2185604",
|
||||||
"build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/73/ef08a58076c8fc37af28bf097628",
|
"build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e0/ee/e6b94bf4149530e412cfe27e5cb4",
|
||||||
"build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/97/62/0944cd3ab34d681cd7c9bfaa0b11",
|
"build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/97/62/0944cd3ab34d681cd7c9bfaa0b11",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9c/c0/ab01fbc7544f451db865b93e5430",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/a5/c73d18aba833987ff3e713bbf981",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/27/e1/de75aac52e10bebae81fc12aa030",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/27/e1/de75aac52e10bebae81fc12aa030",
|
||||||
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d0/53/f358c93ad50992c3aee613034e38",
|
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a6/81/ee957a5bfd1be45c0e865f8a27ba",
|
||||||
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/03/29/abffbfc56fd981915253f1d4ed96",
|
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/03/29/abffbfc56fd981915253f1d4ed96",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a4/83/bf81025600a3a440d359057f0e05",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/04/35/104446e5f91b9fe35fa413be02ca",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/c0/68b44a693639308981b5214f02c1",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/c0/68b44a693639308981b5214f02c1",
|
||||||
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5b/f8/e1934b7fd16e75bdf9dadccef22b",
|
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b5/c4/5935bc59cc237c42e8ac9be47ce5",
|
||||||
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1d/ad/193fc3816804c48954a6b2901b49",
|
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/35/3d/f04ebd4a7d066088595b8ed4bbc5",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/89/c5/0fcd0aafe25cb054f165b3ede0e5",
|
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/72/eb/25cf435771601aeec273a92ffb50",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ae/2c/bfa29fcf8e83db1cc5bb32da1178",
|
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/45/fb/166e932a6235613935ccf2e51d00",
|
||||||
"build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f4/59/16c01f646c16bb480a197aa9e6e3",
|
"build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f4/59/16c01f646c16bb480a197aa9e6e3",
|
||||||
"build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/81/73/a321fe4aa721d07f2a44257967cf",
|
"build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/81/73/a321fe4aa721d07f2a44257967cf",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/99/99/2adf5a22923eed3f8b5667ee8220",
|
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/99/99/2adf5a22923eed3f8b5667ee8220",
|
||||||
@ -3960,12 +3960,12 @@
|
|||||||
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/3a/c747993afbf4c1ed1c5e8b0e5d5a",
|
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/3a/c747993afbf4c1ed1c5e8b0e5d5a",
|
||||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/21/31fe300afbf7d9da766c04064919",
|
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/21/31fe300afbf7d9da766c04064919",
|
||||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/bd/2b05dbfd98cd55cedf924b639765",
|
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/bd/2b05dbfd98cd55cedf924b639765",
|
||||||
"build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a9/1d/3896630323d201928a9a94d1132b",
|
"build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5b/6d/877a5a015eb3d705795a2db7fa74",
|
||||||
"build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/39/eb/512862ff8b6e5aca44d4ef9b2b00",
|
"build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/58/a4/3d2a1e4a47c51e883ef29b5e280d",
|
||||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/db/2644a5447ec880608cc719211a03",
|
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a7/ca/91cfd7dc2ccc8c996daf9aaeb9fd",
|
||||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/d6/fe10f5e34d8c86a47ddbea041297",
|
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/cb/de559dd0ea55e12a228ea1cd8974",
|
||||||
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f1/f8/0529ec1e41b71f9e595eff5d9655",
|
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c9/e1/132123edad385ac0a977337f044c",
|
||||||
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b8/e5/4acf3b0f3031a22ef02a8ec32a3d",
|
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f2/e3/a82f88651f2ffef8c52a20da42a6",
|
||||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a4/c5/1ec7762968c5ab8a95d4b2203310",
|
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d4/9c/95e358c6e929bcb046e40fe5ad2b",
|
||||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/23/0a99eea99bcf009a1801eb6c4755"
|
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8e/2d/81ce3feefa50c283cd7a8398ac72"
|
||||||
}
|
}
|
||||||
3
.idea/dictionaries/ericf.xml
generated
3
.idea/dictionaries/ericf.xml
generated
@ -345,6 +345,7 @@
|
|||||||
<w>childnode</w>
|
<w>childnode</w>
|
||||||
<w>childtype</w>
|
<w>childtype</w>
|
||||||
<w>childtypes</w>
|
<w>childtypes</w>
|
||||||
|
<w>childval</w>
|
||||||
<w>chinesetraditional</w>
|
<w>chinesetraditional</w>
|
||||||
<w>chipfork</w>
|
<w>chipfork</w>
|
||||||
<w>chosenone</w>
|
<w>chosenone</w>
|
||||||
@ -491,6 +492,7 @@
|
|||||||
<w>dataval</w>
|
<w>dataval</w>
|
||||||
<w>datetimemodule</w>
|
<w>datetimemodule</w>
|
||||||
<w>datetimes</w>
|
<w>datetimes</w>
|
||||||
|
<w>datetimeval</w>
|
||||||
<w>daynum</w>
|
<w>daynum</w>
|
||||||
<w>dayoffset</w>
|
<w>dayoffset</w>
|
||||||
<w>dbapi</w>
|
<w>dbapi</w>
|
||||||
@ -2283,6 +2285,7 @@
|
|||||||
<w>tself</w>
|
<w>tself</w>
|
||||||
<w>tspc</w>
|
<w>tspc</w>
|
||||||
<w>tstr</w>
|
<w>tstr</w>
|
||||||
|
<w>tupleval</w>
|
||||||
<w>turtledemo</w>
|
<w>turtledemo</w>
|
||||||
<w>tval</w>
|
<w>tval</w>
|
||||||
<w>tvalue</w>
|
<w>tvalue</w>
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -661,7 +661,7 @@ test-assetmanager:
|
|||||||
tests/test_ba/test_assetmanager.py::test_assetmanager
|
tests/test_ba/test_assetmanager.py::test_assetmanager
|
||||||
|
|
||||||
# Individual test with extra output enabled.
|
# Individual test with extra output enabled.
|
||||||
test-dataclasses:
|
test-dataclassio:
|
||||||
@tools/pcommand pytest -o log_cli=true -o log_cli_level=debug -s -vv \
|
@tools/pcommand pytest -o log_cli=true -o log_cli_level=debug -s -vv \
|
||||||
tests/test_efro/test_dataclassio.py
|
tests/test_efro/test_dataclassio.py
|
||||||
|
|
||||||
|
|||||||
@ -164,6 +164,7 @@
|
|||||||
<w>childanntypes</w>
|
<w>childanntypes</w>
|
||||||
<w>childtype</w>
|
<w>childtype</w>
|
||||||
<w>childtypes</w>
|
<w>childtypes</w>
|
||||||
|
<w>childval</w>
|
||||||
<w>chrono</w>
|
<w>chrono</w>
|
||||||
<w>chunksize</w>
|
<w>chunksize</w>
|
||||||
<w>cjief</w>
|
<w>cjief</w>
|
||||||
@ -220,6 +221,7 @@
|
|||||||
<w>datas</w>
|
<w>datas</w>
|
||||||
<w>datav</w>
|
<w>datav</w>
|
||||||
<w>datavec</w>
|
<w>datavec</w>
|
||||||
|
<w>datetimeval</w>
|
||||||
<w>dbgstr</w>
|
<w>dbgstr</w>
|
||||||
<w>dbias</w>
|
<w>dbias</w>
|
||||||
<w>dcioexattrs</w>
|
<w>dcioexattrs</w>
|
||||||
@ -995,6 +997,7 @@
|
|||||||
<w>trimeshes</w>
|
<w>trimeshes</w>
|
||||||
<w>trynum</w>
|
<w>trynum</w>
|
||||||
<w>tself</w>
|
<w>tself</w>
|
||||||
|
<w>tupleval</w>
|
||||||
<w>tval</w>
|
<w>tval</w>
|
||||||
<w>tvos</w>
|
<w>tvos</w>
|
||||||
<w>tweakage</w>
|
<w>tweakage</w>
|
||||||
|
|||||||
@ -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-04-30 for Ballistica version 1.6.0 build 20353</em></h4>
|
<h4><em>last updated on 2021-05-03 for Ballistica version 1.6.0 build 20355</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>
|
||||||
|
|||||||
@ -5,16 +5,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import datetime
|
||||||
from dataclasses import field, dataclass
|
from dataclasses import field, dataclass
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from efro.util import utc_now
|
||||||
from efro.dataclassio import (dataclass_validate, dataclass_from_dict,
|
from efro.dataclassio import (dataclass_validate, dataclass_from_dict,
|
||||||
dataclass_to_dict, prepped)
|
dataclass_to_dict, prepped)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Optional, List, Set, Any, Dict, Sequence, Union
|
from typing import Optional, List, Set, Any, Dict, Sequence, Union, Tuple
|
||||||
|
|
||||||
|
|
||||||
class _EnumTest(Enum):
|
class _EnumTest(Enum):
|
||||||
@ -75,6 +77,8 @@ def test_assign() -> None:
|
|||||||
ssval: Set[str] = field(default_factory=set)
|
ssval: Set[str] = field(default_factory=set)
|
||||||
anyval: Any = 1
|
anyval: Any = 1
|
||||||
dictval: Dict[int, str] = field(default_factory=dict)
|
dictval: Dict[int, str] = field(default_factory=dict)
|
||||||
|
tupleval: Tuple[int, str, bool] = (1, 'foo', False)
|
||||||
|
datetimeval: Optional[datetime.datetime] = None
|
||||||
|
|
||||||
class _TestClass2:
|
class _TestClass2:
|
||||||
pass
|
pass
|
||||||
@ -89,14 +93,20 @@ def test_assign() -> None:
|
|||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
dataclass_from_dict(_TestClass, None) # type: ignore
|
dataclass_from_dict(_TestClass, None) # type: ignore
|
||||||
|
|
||||||
|
now = utc_now()
|
||||||
|
|
||||||
# A dict containing *ALL* values should match what we
|
# A dict containing *ALL* values should match what we
|
||||||
# get when creating a dataclass and then converting back
|
# get when creating a dataclass and then converting back
|
||||||
# to a dict.
|
# to a dict.
|
||||||
dict1 = {
|
dict1 = {
|
||||||
'ival': 1,
|
'ival':
|
||||||
'sval': 'foo',
|
1,
|
||||||
'bval': True,
|
'sval':
|
||||||
'fval': 2.0,
|
'foo',
|
||||||
|
'bval':
|
||||||
|
True,
|
||||||
|
'fval':
|
||||||
|
2.0,
|
||||||
'nval': {
|
'nval': {
|
||||||
'ival': 1,
|
'ival': 1,
|
||||||
'sval': 'bar',
|
'sval': 'bar',
|
||||||
@ -104,12 +114,18 @@ def test_assign() -> None:
|
|||||||
'1': 'foof'
|
'1': 'foof'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'enval': 'test1',
|
'enval':
|
||||||
'oival': 1,
|
'test1',
|
||||||
'osval': 'foo',
|
'oival':
|
||||||
'obval': True,
|
1,
|
||||||
'ofval': 1.0,
|
'osval':
|
||||||
'oenval': 'test2',
|
'foo',
|
||||||
|
'obval':
|
||||||
|
True,
|
||||||
|
'ofval':
|
||||||
|
1.0,
|
||||||
|
'oenval':
|
||||||
|
'test2',
|
||||||
'lsval': ['foo'],
|
'lsval': ['foo'],
|
||||||
'lival': [10],
|
'lival': [10],
|
||||||
'lbval': [False],
|
'lbval': [False],
|
||||||
@ -127,7 +143,12 @@ def test_assign() -> None:
|
|||||||
},
|
},
|
||||||
'dictval': {
|
'dictval': {
|
||||||
'1': 'foo'
|
'1': 'foo'
|
||||||
}
|
},
|
||||||
|
'tupleval': [2, 'foof', True],
|
||||||
|
'datetimeval': [
|
||||||
|
now.year, now.month, now.day, now.hour, now.minute, now.second,
|
||||||
|
now.microsecond
|
||||||
|
],
|
||||||
}
|
}
|
||||||
dc1 = dataclass_from_dict(_TestClass, dict1)
|
dc1 = dataclass_from_dict(_TestClass, dict1)
|
||||||
assert dataclass_to_dict(dc1) == dict1
|
assert dataclass_to_dict(dc1) == dict1
|
||||||
@ -198,6 +219,12 @@ def test_assign() -> None:
|
|||||||
dataclass_from_dict(_TestClass, {'ssval': {}})
|
dataclass_from_dict(_TestClass, {'ssval': {}})
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
dataclass_from_dict(_TestClass, {'ssval': set()})
|
dataclass_from_dict(_TestClass, {'ssval': set()})
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_from_dict(_TestClass, {'tupleval': []})
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_from_dict(_TestClass, {'tupleval': [1, 1, 1]})
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
dataclass_from_dict(_TestClass, {'tupleval': [2, 'foof', True, True]})
|
||||||
|
|
||||||
# Fields with type Any should accept all types which are directly
|
# Fields with type Any should accept all types which are directly
|
||||||
# supported by json, but not ones such as tuples or non-string dict keys
|
# supported by json, but not ones such as tuples or non-string dict keys
|
||||||
@ -237,6 +264,14 @@ def test_assign() -> None:
|
|||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
dataclass_from_dict(_TestClass, {'fval': []}, coerce_to_float=True)
|
dataclass_from_dict(_TestClass, {'fval': []}, coerce_to_float=True)
|
||||||
|
|
||||||
|
# Datetime values should only be allowed with timezone set as utc.
|
||||||
|
dataclass_to_dict(_TestClass(datetimeval=utc_now()))
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
dataclass_to_dict(_TestClass(datetimeval=datetime.datetime.now()))
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
# This doesn't actually set timezone on the datetime obj.
|
||||||
|
dataclass_to_dict(_TestClass(datetimeval=datetime.datetime.utcnow()))
|
||||||
|
|
||||||
|
|
||||||
def test_coerce() -> None:
|
def test_coerce() -> None:
|
||||||
"""Test value coercion."""
|
"""Test value coercion."""
|
||||||
|
|||||||
@ -8,6 +8,8 @@ unrecognized attribute data, allowing older clients to interact with newer
|
|||||||
data formats in a nondestructive manner.
|
data formats in a nondestructive manner.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
|
|
||||||
# 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).
|
||||||
# pylint: disable=unidiomatic-typecheck
|
# pylint: disable=unidiomatic-typecheck
|
||||||
@ -18,12 +20,22 @@ import logging
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import typing
|
import typing
|
||||||
|
import datetime
|
||||||
from typing import TYPE_CHECKING, TypeVar, Generic, get_type_hints
|
from typing import TYPE_CHECKING, TypeVar, Generic, get_type_hints
|
||||||
|
|
||||||
from efro.util import enum_by_value
|
from efro.util import enum_by_value
|
||||||
|
|
||||||
|
_pytz_utc: Any
|
||||||
|
|
||||||
|
# We don't *require* pytz but we want to support it for tzinfos if available.
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
_pytz_utc = pytz.utc
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
_pytz_utc = None # pylint: disable=invalid-name
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Any, Dict, Type, Tuple, Optional
|
from typing import Any, Dict, Type, Tuple, Optional, List
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
@ -332,10 +344,35 @@ class PrepSession:
|
|||||||
recursion_level=recursion_level + 1)
|
recursion_level=recursion_level + 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# For Tuples, simply check individual member types.
|
||||||
|
# (and, for now, explicitly disallow zero member types or usage
|
||||||
|
# of ellipsis)
|
||||||
|
if origin is tuple:
|
||||||
|
childtypes = typing.get_args(anntype)
|
||||||
|
if not childtypes:
|
||||||
|
raise TypeError(
|
||||||
|
f'Tuple at \'{attrname}\''
|
||||||
|
f' has no type args; dataclassio requires type args.')
|
||||||
|
if childtypes[-1] is ...:
|
||||||
|
raise TypeError(f'Found ellipsis as part of type for'
|
||||||
|
f' \'{attrname}\' on {cls}; these are not'
|
||||||
|
f' supported by dataclassio.')
|
||||||
|
for childtype in childtypes:
|
||||||
|
self.prep_type(cls,
|
||||||
|
attrname,
|
||||||
|
childtype,
|
||||||
|
recursion_level=recursion_level + 1)
|
||||||
|
return
|
||||||
|
|
||||||
if issubclass(origin, Enum):
|
if issubclass(origin, Enum):
|
||||||
self.prep_enum(origin)
|
self.prep_enum(origin)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# We allow datetime objects (and google's extended subclass of them
|
||||||
|
# used in firestore, which is why we don't look for exact type here).
|
||||||
|
if issubclass(origin, datetime.datetime):
|
||||||
|
return
|
||||||
|
|
||||||
if dataclasses.is_dataclass(origin):
|
if dataclasses.is_dataclass(origin):
|
||||||
self.prep_dataclass(origin, recursion_level=recursion_level + 1)
|
self.prep_dataclass(origin, recursion_level=recursion_level + 1)
|
||||||
return
|
return
|
||||||
@ -475,6 +512,7 @@ class _Outputter:
|
|||||||
value: Any) -> Any:
|
value: Any) -> Any:
|
||||||
# pylint: disable=too-many-return-statements
|
# pylint: disable=too-many-return-statements
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
|
# pylint: disable=too-many-statements
|
||||||
|
|
||||||
origin = _get_origin(anntype)
|
origin = _get_origin(anntype)
|
||||||
|
|
||||||
@ -513,6 +551,27 @@ class _Outputter:
|
|||||||
_raise_type_error(fieldpath, type(value), (origin, ))
|
_raise_type_error(fieldpath, type(value), (origin, ))
|
||||||
return value if self._create else None
|
return value if self._create else None
|
||||||
|
|
||||||
|
if origin is tuple:
|
||||||
|
if not isinstance(value, tuple):
|
||||||
|
raise TypeError(f'Expected a tuple for {fieldpath};'
|
||||||
|
f' found a {type(value)}')
|
||||||
|
childanntypes = typing.get_args(anntype)
|
||||||
|
|
||||||
|
# We should have verified this was non-zero at prep-time
|
||||||
|
assert childanntypes
|
||||||
|
if len(value) != len(childanntypes):
|
||||||
|
raise TypeError(f'Tuple at {fieldpath} contains'
|
||||||
|
f' {len(value)} values; type specifies'
|
||||||
|
f' {len(childanntypes)}.')
|
||||||
|
if self._create:
|
||||||
|
return [
|
||||||
|
self._process_value(cls, fieldpath, childanntypes[i], x)
|
||||||
|
for i, x in enumerate(value)
|
||||||
|
]
|
||||||
|
for i, x in enumerate(value):
|
||||||
|
self._process_value(cls, fieldpath, childanntypes[i], x)
|
||||||
|
return None
|
||||||
|
|
||||||
if origin is list:
|
if origin is list:
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise TypeError(f'Expected a list for {fieldpath};'
|
raise TypeError(f'Expected a list for {fieldpath};'
|
||||||
@ -585,6 +644,20 @@ class _Outputter:
|
|||||||
# types, so we can blindly return it here.
|
# types, so we can blindly return it here.
|
||||||
return value.value if self._create else None
|
return value.value if self._create else None
|
||||||
|
|
||||||
|
if issubclass(origin, datetime.datetime):
|
||||||
|
if not isinstance(value, origin):
|
||||||
|
raise TypeError(f'Expected a {origin} for {fieldpath};'
|
||||||
|
f' found a {type(value)}.')
|
||||||
|
# We only support timezone-aware utc times.
|
||||||
|
if (value.tzinfo is not datetime.timezone.utc
|
||||||
|
and (_pytz_utc is None or value.tzinfo is not _pytz_utc)):
|
||||||
|
raise ValueError(
|
||||||
|
'datetime values must have timezone set as timezone.utc')
|
||||||
|
return [
|
||||||
|
value.year, value.month, value.day, value.hour, value.minute,
|
||||||
|
value.second, value.microsecond
|
||||||
|
] if self._create else None
|
||||||
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
|
f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
|
||||||
|
|
||||||
@ -677,6 +750,7 @@ class _Inputter(Generic[T]):
|
|||||||
value: Any) -> Any:
|
value: Any) -> Any:
|
||||||
"""Convert an assigned value to what a dataclass field expects."""
|
"""Convert an assigned value to what a dataclass field expects."""
|
||||||
# pylint: disable=too-many-return-statements
|
# pylint: disable=too-many-return-statements
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
|
|
||||||
origin = _get_origin(anntype)
|
origin = _get_origin(anntype)
|
||||||
|
|
||||||
@ -718,6 +792,9 @@ class _Inputter(Generic[T]):
|
|||||||
return self._sequence_from_input(cls, fieldpath, anntype, value,
|
return self._sequence_from_input(cls, fieldpath, anntype, value,
|
||||||
origin)
|
origin)
|
||||||
|
|
||||||
|
if origin is tuple:
|
||||||
|
return self._tuple_from_input(cls, fieldpath, anntype, value)
|
||||||
|
|
||||||
if origin is dict:
|
if origin is dict:
|
||||||
return self._dict_from_input(cls, fieldpath, anntype, value)
|
return self._dict_from_input(cls, fieldpath, anntype, value)
|
||||||
|
|
||||||
@ -727,6 +804,9 @@ class _Inputter(Generic[T]):
|
|||||||
if issubclass(origin, Enum):
|
if issubclass(origin, Enum):
|
||||||
return enum_by_value(origin, value)
|
return enum_by_value(origin, value)
|
||||||
|
|
||||||
|
if issubclass(origin, datetime.datetime):
|
||||||
|
return self._datetime_from_input(cls, fieldpath, value)
|
||||||
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
|
f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
|
||||||
|
|
||||||
@ -901,3 +981,56 @@ class _Inputter(Generic[T]):
|
|||||||
return seqtype(
|
return seqtype(
|
||||||
self._value_from_input(cls, fieldpath, childanntype, i)
|
self._value_from_input(cls, fieldpath, childanntype, i)
|
||||||
for i in value)
|
for i in value)
|
||||||
|
|
||||||
|
def _datetime_from_input(self, cls: Type, fieldpath: str,
|
||||||
|
value: Any) -> Any:
|
||||||
|
|
||||||
|
# We expect a list of 7 ints.
|
||||||
|
if type(value) is not list:
|
||||||
|
raise TypeError(
|
||||||
|
f'Invalid input value for "{fieldpath}" on "{cls}";'
|
||||||
|
f' expected a list, got a {type(value).__name__}')
|
||||||
|
if len(value) != 7 or not all(isinstance(x, int) for x in value):
|
||||||
|
raise TypeError(
|
||||||
|
f'Invalid input value for "{fieldpath}" on "{cls}";'
|
||||||
|
f' expected a list of 7 ints.')
|
||||||
|
return datetime.datetime( # type: ignore
|
||||||
|
*value, tzinfo=datetime.timezone.utc)
|
||||||
|
|
||||||
|
def _tuple_from_input(self, cls: Type, fieldpath: str, anntype: Any,
|
||||||
|
value: Any) -> Any:
|
||||||
|
|
||||||
|
out: List = []
|
||||||
|
|
||||||
|
# Because we are json-centric, we expect a list for all sequences.
|
||||||
|
if type(value) is not list:
|
||||||
|
raise TypeError(f'Invalid input value for "{fieldpath}";'
|
||||||
|
f' expected a list, got a {type(value).__name__}')
|
||||||
|
|
||||||
|
childanntypes = typing.get_args(anntype)
|
||||||
|
|
||||||
|
# We should have verified this to be non-zero at prep-time.
|
||||||
|
assert childanntypes
|
||||||
|
|
||||||
|
if len(value) != len(childanntypes):
|
||||||
|
raise TypeError(f'Invalid tuple input for "{fieldpath}";'
|
||||||
|
f' expected {len(childanntypes)} values,'
|
||||||
|
f' found {len(value)}.')
|
||||||
|
|
||||||
|
for i, childanntype in enumerate(childanntypes):
|
||||||
|
childval = value[i]
|
||||||
|
|
||||||
|
# 'Any' type children; make sure they are valid json values
|
||||||
|
# and then just grab them.
|
||||||
|
if childanntype is typing.Any:
|
||||||
|
if not _is_valid_json(childval):
|
||||||
|
raise TypeError(f'Item {i} of {fieldpath} contains'
|
||||||
|
f' data type(s) not supported by json.')
|
||||||
|
out.append(childval)
|
||||||
|
else:
|
||||||
|
out.append(
|
||||||
|
self._value_from_input(cls, fieldpath, childanntype,
|
||||||
|
childval))
|
||||||
|
|
||||||
|
assert len(out) == len(childanntypes)
|
||||||
|
return tuple(out)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user