mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +08:00
added enum fallback option to dataclassio
This commit is contained in:
parent
bc808c65fc
commit
2cb0bf0f4f
72
.efrocachemap
generated
72
.efrocachemap
generated
@ -4099,42 +4099,42 @@
|
||||
"build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1",
|
||||
"build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718",
|
||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "809ad854c769646c9bccf9f72bfd2f9f",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "84f339cbec284e824904613e59cab1a2",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "2c385ed88eaf7787c4bf36b2681ccd91",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "2156812371a87399fb3e0fff856fc739",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "40b5aacb91774c9343d674a15801f6d9",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "8833e259a48cdb79a958d0ed18b56a65",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b5b8df8478e0d8513f6273d00bf97779",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "e016a4a952ee8947169a2dfc34718a9f",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fffe409a157aa345ba9193f992d2b752",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "df686ee58c36af1100d20fa7a4deb08e",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "70373843b698d5c4400a9cbb916d8d21",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "f68567c5d2d5ea680c26578c2dc7a912",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "71137c9d5d96923d4d5687de021488cd",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "f37c138bc5b6c17effa49159cc30db91",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "fecc9bbd4ebb50b633313077206c1504",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "2d30efa541a0bd38fe578ce0baf929a6",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "e8b47cf217d05ce0df5a334e3b456ced",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "014014670516ce58a7d7dd3c39f12c84",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "e8b47cf217d05ce0df5a334e3b456ced",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "014014670516ce58a7d7dd3c39f12c84",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "713c6046e9e061ac045c65f593a1dafb",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "107aa28e8e7568ff6fca408728b80d94",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "713c6046e9e061ac045c65f593a1dafb",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "107aa28e8e7568ff6fca408728b80d94",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "ffe19b0ae94217a0ed7ccc3d46c97a24",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "3f3a18dc4d6118ec181d03df1d0bd4de",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "ffe19b0ae94217a0ed7ccc3d46c97a24",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "3f3a18dc4d6118ec181d03df1d0bd4de",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6eeb79220b31acc56da748201090218f",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "ade915a77be68916691c0329f8cfdd9f",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "67b55ef9f0d683340ef61c1df08de181",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "689fd22835bb965b5e7fafe5149f8319",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "fdc1fa0dbaca6e517f2608bcc7476e96",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6af6ad18c0486410454db2a53b72e458",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "f496932bed2c235654829404741b4526",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5ec6c0c883ca79b2eec1f7b952ecaa39",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "0921e644948438c78c8621f1199dc6d6",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "54486e895c6be0128a2e25624aaef051",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "bcfc357760d1030c66e3b65c30a649c8",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a398b7f179cf6ee31af8037bae59bdc4",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "9c5e37721e8834491c249b3f1e986e8a",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "57dd4fa7356ea5a1a102c5a0422a4485",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cad5283f88be83233e7a8b674db02c17",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f36a3b1ef5d5650c373ac3230f3e64a3",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "327abf1bd532b104cbb63fa9dd11f9b7",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "685e787af247ba2694c7632c469e4853",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "6c41aad3f4c60d56350be66a6ba28a3f",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "d752bc8ee063e3d95038b3ce8bdb0e3c",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5f2e7c646ce28814d5d87ba2e4b826c4",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "b440e073afd70d018fc14ee10f41bb85",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "bc56615fbc3d671deebf36942b36c706",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e5d47e94497951d438520cde86d487b2",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "86eaadb3dee0a1f1137192fe943996f6",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "8c795d1a871f0d82a198b5aeb506d770",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "86eaadb3dee0a1f1137192fe943996f6",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "8c795d1a871f0d82a198b5aeb506d770",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "ab0cf0e9d6001748927660d84c1da87f",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "62c22d7a25fd62831cb4a089bbee1b0b",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "ab0cf0e9d6001748927660d84c1da87f",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "62c22d7a25fd62831cb4a089bbee1b0b",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "75a5ab56d54304e602fff8dae6435904",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "19b6e806d066affc80b32c4899c6ba2f",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "75a5ab56d54304e602fff8dae6435904",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "19b6e806d066affc80b32c4899c6ba2f",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ca6096df15041b6af9a89ed81880c98b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f06c730c9cee3599679df778ec565842",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "1a77179b3548e78791ddd1205ff505a3",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d755dc558bba71715ed0b543fa966be4",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "722aff67a010cc0a435bc70018b0b49a",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "373deb2c02283978f415c1b937229292",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "fe26aafc763a1198e1921caeb261bdb8",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "73f3c9211175f452e15c45296db89744",
|
||||
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
|
||||
"src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad",
|
||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "3a583e7e03bd4907b21adc3bf5729d15",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
### 1.7.37 (build 22110, api 9, 2024-11-22)
|
||||
### 1.7.37 (build 22112, api 9, 2024-11-23)
|
||||
- Bumping api version to 9. As you'll see below, there's some UI changes that
|
||||
will require a bit of work for any UI mods to adapt to. If your mods don't
|
||||
touch UI stuff at all you can simply bump your api version and call it a day.
|
||||
|
||||
@ -25,6 +25,7 @@ if TYPE_CHECKING:
|
||||
from typing import Callable, Any
|
||||
|
||||
from efro.call import CallbackRegistration
|
||||
from bacommon.cloud import ClassicAccountData
|
||||
from babase import AppIntent, AccountV2Handle, CloudSubscription
|
||||
from bauiv1 import UIV1AppSubsystem, MainWindow, MainWindowState
|
||||
|
||||
@ -38,6 +39,7 @@ class ClassicAppMode(AppMode):
|
||||
CallbackRegistration | None
|
||||
) = None
|
||||
self._test_sub: CloudSubscription | None = None
|
||||
self._account_data_sub: CloudSubscription | None = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
@ -122,7 +124,7 @@ class ClassicAppMode(AppMode):
|
||||
|
||||
# We want to be informed when primary account changes.
|
||||
self._on_primary_account_changed_callback = (
|
||||
app.plus.accounts.on_primary_account_changed_callbacks.add(
|
||||
app.plus.accounts.on_primary_account_changed_callbacks.register(
|
||||
self.update_for_primary_account
|
||||
)
|
||||
)
|
||||
@ -135,7 +137,7 @@ class ClassicAppMode(AppMode):
|
||||
# Stop being informed of account changes.
|
||||
self._on_primary_account_changed_callback = None
|
||||
|
||||
# Remove any listeners for any current primary account.
|
||||
# Remove anything following any current account.
|
||||
self.update_for_primary_account(None)
|
||||
|
||||
# Save where we were in the UI so we return there next time.
|
||||
@ -157,10 +159,10 @@ class ClassicAppMode(AppMode):
|
||||
) -> None:
|
||||
"""Update subscriptions/etc. for a new primary account state."""
|
||||
assert in_logic_thread()
|
||||
assert app.plus is not None
|
||||
|
||||
# For testing subscription functionality.
|
||||
if os.environ.get('BA_SUBSCRIPTION_TEST') == '1':
|
||||
assert app.plus is not None
|
||||
if account is None:
|
||||
self._test_sub = None
|
||||
else:
|
||||
@ -171,9 +173,22 @@ class ClassicAppMode(AppMode):
|
||||
else:
|
||||
self._test_sub = None
|
||||
|
||||
if account is None or bool(True):
|
||||
self._account_data_sub = None
|
||||
else:
|
||||
with account:
|
||||
self._account_data_sub = (
|
||||
app.plus.cloud.subscribe_classic_account_data(
|
||||
self._on_classic_account_data_change
|
||||
)
|
||||
)
|
||||
|
||||
def _on_sub_test_update(self, val: int | None) -> None:
|
||||
print(f'GOT SUB TEST UPDATE: {val}')
|
||||
|
||||
def _on_classic_account_data_change(self, val: ClassicAccountData) -> None:
|
||||
print(f'GOT CLASSIC ACCOUNT DATA: {val}')
|
||||
|
||||
def _root_ui_menu_press(self) -> None:
|
||||
from babase import push_back_press
|
||||
|
||||
@ -184,7 +199,6 @@ class ClassicAppMode(AppMode):
|
||||
if old_window is not None:
|
||||
|
||||
classic = app.classic
|
||||
|
||||
assert classic is not None
|
||||
classic.resume()
|
||||
|
||||
@ -230,7 +244,13 @@ class ClassicAppMode(AppMode):
|
||||
win_type: type[MainWindow],
|
||||
win_create_call: Callable[[], MainWindow],
|
||||
) -> None:
|
||||
"""Navigate to or away from a particular type of Auxiliary window."""
|
||||
"""Navigate to or away from an Auxiliary window.
|
||||
|
||||
Auxiliary windows can be thought of as 'side quests' in the
|
||||
window hierarchy; places such as settings windows or league
|
||||
ranking windows that the user might want to visit without losing
|
||||
their place in the regular hierarchy.
|
||||
"""
|
||||
# pylint: disable=unidiomatic-typecheck
|
||||
|
||||
ui = app.ui_v1
|
||||
@ -286,8 +306,8 @@ class ClassicAppMode(AppMode):
|
||||
)
|
||||
return
|
||||
|
||||
# Ok, no auxiliary states found. Now if current window is auxiliary
|
||||
# and the type matches, simply do a back.
|
||||
# Ok, no auxiliary states found. Now if current window is
|
||||
# auxiliary and the type matches, simply do a back.
|
||||
if (
|
||||
current_main_window.main_window_is_auxiliary
|
||||
and type(current_main_window) is win_type
|
||||
|
||||
@ -53,7 +53,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 22110
|
||||
TARGET_BALLISTICA_BUILD = 22112
|
||||
TARGET_BALLISTICA_VERSION = '1.7.37'
|
||||
|
||||
|
||||
|
||||
@ -177,7 +177,15 @@ class CloudSubsystem(babase.AppSubsystem):
|
||||
def subscribe_test(
|
||||
self, updatecall: Callable[[int | None], None]
|
||||
) -> babase.CloudSubscription:
|
||||
"""Subscribe to some data."""
|
||||
"""Subscribe to some test data."""
|
||||
raise NotImplementedError(
|
||||
'Cloud functionality is not present in this build.'
|
||||
)
|
||||
|
||||
def subscribe_classic_account_data(
|
||||
self, updatecall: Callable[[bacommon.cloud.ClassicAccountData], None]
|
||||
) -> babase.CloudSubscription:
|
||||
"""Subscribe to classic account data."""
|
||||
raise NotImplementedError(
|
||||
'Cloud functionality is not present in this build.'
|
||||
)
|
||||
|
||||
@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kEngineBuildNumber = 22110;
|
||||
const int kEngineBuildNumber = 22112;
|
||||
const char* kEngineVersion = "1.7.37";
|
||||
const int kEngineApiVersion = 9;
|
||||
|
||||
|
||||
@ -588,7 +588,8 @@ def test_dict() -> None:
|
||||
|
||||
obj = _TestClass(dval={})
|
||||
|
||||
# 'Any' dicts should only support values directly compatible with json.
|
||||
# 'Any' dicts should only support values directly compatible with
|
||||
# json.
|
||||
obj.dval['foo'] = 5
|
||||
dataclass_to_dict(obj)
|
||||
with pytest.raises(TypeError):
|
||||
@ -598,8 +599,8 @@ def test_dict() -> None:
|
||||
obj.dval['foo'] = _GoodEnum.VAL1
|
||||
dataclass_to_dict(obj)
|
||||
|
||||
# Int dict-keys should actually be stored as strings internally
|
||||
# (for json compatibility).
|
||||
# Int dict-keys should actually be stored as strings internally (for
|
||||
# json compatibility).
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass2:
|
||||
@ -753,8 +754,8 @@ def test_any() -> None:
|
||||
|
||||
obj = _TestClass(anyval=b'bytes')
|
||||
|
||||
# JSON output doesn't allow bytes or datetime objects
|
||||
# included in 'Any' data.
|
||||
# JSON output doesn't allow bytes or datetime objects included in
|
||||
# 'Any' data.
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_validate(obj, codec=Codec.JSON)
|
||||
|
||||
@ -831,8 +832,8 @@ def test_datetime_limits() -> None:
|
||||
def test_field_paths() -> None:
|
||||
"""Test type-safe field path evaluations."""
|
||||
|
||||
# Define a few nested dataclass types, some of which
|
||||
# have storage names differing from their field names.
|
||||
# Define a few nested dataclass types, some of which have storage
|
||||
# names differing from their field names.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass:
|
||||
@ -856,13 +857,13 @@ def test_field_paths() -> None:
|
||||
assert lookup.path(lambda obj: obj.sub2.val1) == 's2.val1'
|
||||
assert lookup.path(lambda obj: obj.sub2.val2) == 's2.v2'
|
||||
|
||||
# Attempting to return fields that aren't there should fail
|
||||
# in both type-checking and runtime.
|
||||
# Attempting to return fields that aren't there should fail in both
|
||||
# type-checking and runtime.
|
||||
with pytest.raises(AttributeError):
|
||||
lookup.path(lambda obj: obj.sub1.val3) # type: ignore
|
||||
|
||||
# Returning non-field objects will fail at runtime
|
||||
# even if type-checking evaluates them as valid values.
|
||||
# Returning non-field objects will fail at runtime even if
|
||||
# type-checking evaluates them as valid values.
|
||||
with pytest.raises(TypeError):
|
||||
lookup.path(lambda obj: 1)
|
||||
|
||||
@ -905,8 +906,8 @@ def test_extended_data() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
_obj = dataclass_from_dict(_TestClass, indata)
|
||||
|
||||
# Now define the same data but give it an adapter
|
||||
# so it can work with our incorrectly-formatted data.
|
||||
# Now define the same data but give it an adapter so it can work
|
||||
# with our incorrectly-formatted data.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass2(IOExtendedData):
|
||||
@ -978,8 +979,8 @@ def test_soft_default() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
dataclass_from_dict(_TestClassA2, {})
|
||||
|
||||
# These should succeed because it has a soft-default value to
|
||||
# fall back on.
|
||||
# These should succeed because it has a soft-default value to fall
|
||||
# back on.
|
||||
dataclass_from_dict(_TestClassB, {})
|
||||
dataclass_from_dict(_TestClassB2, {})
|
||||
dataclass_from_dict(_TestClassB3, {})
|
||||
@ -1020,8 +1021,8 @@ def test_soft_default() -> None:
|
||||
|
||||
assert dataclass_to_dict(_TestClassC3b(0)) == {}
|
||||
|
||||
# We disallow passing a few mutable types as soft_defaults
|
||||
# just as dataclass does with regular defaults.
|
||||
# We disallow passing a few mutable types as soft_defaults just as
|
||||
# dataclass does with regular defaults.
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@ -1044,9 +1045,9 @@ def test_soft_default() -> None:
|
||||
class _TestClassD3:
|
||||
lval: Annotated[dict, IOAttrs(soft_default={})]
|
||||
|
||||
# soft_defaults are not static-type-checked, but we do try to
|
||||
# catch basic type mismatches at prep time. Make sure that's working.
|
||||
# (we also do full value validation during input, but the more we catch
|
||||
# soft_defaults are not static-type-checked, but we do try to catch
|
||||
# basic type mismatches at prep time. Make sure that's working. (we
|
||||
# also do full value validation during input, but the more we catch
|
||||
# early the better)
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ -1069,9 +1070,9 @@ def test_soft_default() -> None:
|
||||
class _TestClassE3:
|
||||
lval: Annotated[list, IOAttrs(soft_default_factory=set)]
|
||||
|
||||
# Make sure Unions/Optionals go through ok.
|
||||
# (note that mismatches currently aren't caught at prep time; just
|
||||
# checking the negative case here).
|
||||
# Make sure Unions/Optionals go through ok. Note that mismatches
|
||||
# currently aren't caught at prep time; just checking the negative
|
||||
# case here.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE4:
|
||||
@ -1083,7 +1084,8 @@ def test_soft_default() -> None:
|
||||
lval: Annotated[str | None, IOAttrs(soft_default='foo')]
|
||||
|
||||
# Now try more in-depth examples: nested type mismatches like this
|
||||
# are currently not caught at prep-time but ARE caught during inputting.
|
||||
# are currently not caught at prep-time but ARE caught during
|
||||
# inputting.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE6:
|
||||
@ -1100,9 +1102,9 @@ def test_soft_default() -> None:
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_from_dict(_TestClassE7, {})
|
||||
|
||||
# If both a soft_default and regular field default are present,
|
||||
# make sure soft_default takes precedence (it applies before
|
||||
# data even hits the dataclass constructor).
|
||||
# If both a soft_default and regular field default are present, make
|
||||
# sure soft_default takes precedence (it applies before data even
|
||||
# hits the dataclass constructor).
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
@ -1111,8 +1113,8 @@ def test_soft_default() -> None:
|
||||
|
||||
assert dataclass_from_dict(_TestClassE8, {}).ival == 1
|
||||
|
||||
# Make sure soft_default gets used both when determining when
|
||||
# to omit values from output and what to recreate missing values as.
|
||||
# Make sure soft_default gets used both when determining when to
|
||||
# omit values from output and what to recreate missing values as.
|
||||
orig = _TestClassE8(ival=1)
|
||||
todict = dataclass_to_dict(orig)
|
||||
assert todict == {}
|
||||
@ -1168,11 +1170,10 @@ class MTTestBase(IOMultiType[MTTestTypeID]):
|
||||
@classmethod
|
||||
def get_type_id(cls) -> MTTestTypeID:
|
||||
"""Provide the type-id for this subclass."""
|
||||
# If we wanted, we could just maintain a static mapping
|
||||
# of types-to-ids here, but there are benefits to letting
|
||||
# each child class speak for itself. Namely that we can
|
||||
# do lazy-loading and don't need to have all types present
|
||||
# here.
|
||||
# If we wanted, we could just maintain a static mapping of
|
||||
# types-to-ids here, but there are benefits to letting each
|
||||
# child class speak for itself. Namely that we can do
|
||||
# lazy-loading and don't need to have all types present here.
|
||||
|
||||
# So we'll let all our child classes override this.
|
||||
raise NotImplementedError()
|
||||
@ -1457,3 +1458,58 @@ def test_multi_type_2() -> None:
|
||||
indict3 = {'type': 'm3'}
|
||||
with pytest.raises(RuntimeError):
|
||||
val3 = dataclass_from_dict(MTTest2Base, indict3)
|
||||
|
||||
|
||||
def test_enum_fallback() -> None:
|
||||
"""Test enum_fallback IOAttr values."""
|
||||
# pylint: disable=missing-class-docstring
|
||||
# pylint: disable=unused-variable
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestClass:
|
||||
|
||||
class TestEnum1(Enum):
|
||||
VAL1 = 'val1'
|
||||
VAL2 = 'val2'
|
||||
VAL3 = 'val3'
|
||||
|
||||
class TestEnum2(Enum):
|
||||
VAL1 = 'val1'
|
||||
VAL2 = 'val2'
|
||||
VAL3 = 'val3'
|
||||
|
||||
enum1val: Annotated[TestEnum1, IOAttrs('e1')]
|
||||
enum2val: Annotated[
|
||||
TestEnum2, IOAttrs('e2', enum_fallback=TestEnum2.VAL1)
|
||||
]
|
||||
|
||||
# All valid values; should work.
|
||||
_obj = dataclass_from_dict(TestClass, {'e1': 'val1', 'e2': 'val1'})
|
||||
|
||||
# Bad Enum1 value; should fail since there's no fallback.
|
||||
with pytest.raises(ValueError):
|
||||
_obj = dataclass_from_dict(TestClass, {'e1': 'val4', 'e2': 'val1'})
|
||||
|
||||
# Bad Enum2 value; should substitute our fallback value.
|
||||
obj = dataclass_from_dict(TestClass, {'e1': 'val1', 'e2': 'val4'})
|
||||
assert obj.enum2val is obj.TestEnum2.VAL1
|
||||
|
||||
# Using wrong type as enum_fallback should fail.
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestClass2:
|
||||
|
||||
class TestEnum1(Enum):
|
||||
VAL1 = 'val1'
|
||||
VAL2 = 'val2'
|
||||
|
||||
class TestEnum2(Enum):
|
||||
VAL1 = 'val1'
|
||||
VAL2 = 'val2'
|
||||
|
||||
enum1val: Annotated[
|
||||
TestEnum1, IOAttrs('e1', enum_fallback=TestEnum2.VAL1)
|
||||
]
|
||||
|
||||
@ -323,3 +323,12 @@ class BSPrivatePartyResponse(Response):
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
datacode: Annotated[str | None, IOAttrs('d')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicAccountData:
|
||||
"""Account related data for classic app mode."""
|
||||
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
|
||||
@ -17,10 +17,11 @@ if TYPE_CHECKING:
|
||||
class CallbackSet(Generic[T]):
|
||||
"""A simple way to manage a set of callbacks.
|
||||
|
||||
Any number of calls can be added to a callback set. Each add results
|
||||
in an entry that can be used to remove the call from the set later.
|
||||
Callbacks are also implicitly removed when an entry is deallocated,
|
||||
so make sure to hold on to the return value when adding.
|
||||
Any number of calls can be registered with a callback set. Each
|
||||
registration results in a Registration object that can be used to
|
||||
deregister the call from the set later. Callbacks are also
|
||||
implicitly deregistered when an entry is deallocated, so make sure
|
||||
to hold on to the return value when adding.
|
||||
|
||||
CallbackSet instances should be used from a single thread only
|
||||
(this will be checked in debug mode).
|
||||
@ -32,8 +33,8 @@ class CallbackSet(Generic[T]):
|
||||
if __debug__:
|
||||
self.thread = threading.current_thread()
|
||||
|
||||
def add(self, call: T) -> CallbackRegistration[T]:
|
||||
"""Add a callback."""
|
||||
def register(self, call: T) -> CallbackRegistration[T]:
|
||||
"""Register a new callback."""
|
||||
assert threading.current_thread() == self.thread
|
||||
|
||||
self._prune()
|
||||
@ -53,7 +54,8 @@ class CallbackSet(Generic[T]):
|
||||
|
||||
self._prune()
|
||||
|
||||
# Ignore calls that have been deallocated or explicitly cleared.
|
||||
# Ignore calls that have been deallocated or explicitly
|
||||
# deregistered.
|
||||
entries = [e() for e in self._entries]
|
||||
return [e.call for e in entries if e is not None and e.call is not None]
|
||||
|
||||
@ -69,6 +71,7 @@ class CallbackSet(Generic[T]):
|
||||
if not needs_prune:
|
||||
return
|
||||
|
||||
# Ok; something needs pruning. Rebuild the entries list.
|
||||
newentries: list[weakref.ref[CallbackRegistration[T]]] = []
|
||||
for entry in self._entries:
|
||||
entrytarget = entry()
|
||||
@ -84,7 +87,7 @@ class CallbackRegistration(Generic[T]):
|
||||
self.call: T | None = call
|
||||
self.callbackset: CallbackSet[T] | None = callbackset
|
||||
|
||||
def clear(self) -> None:
|
||||
def deregister(self) -> None:
|
||||
"""Explicitly remove a callback from a CallbackSet."""
|
||||
assert (
|
||||
self.callbackset is None
|
||||
|
||||
@ -160,6 +160,8 @@ class IOAttrs:
|
||||
fields; it should be used instead of 'soft_default' for mutable types
|
||||
such as lists to prevent a single default object from unintentionally
|
||||
changing over time.
|
||||
'enum_fallback', if provided, specifies an enum value to be substituted
|
||||
in the case of unrecognized enum values.
|
||||
"""
|
||||
|
||||
# A sentinel object to detect if a parameter is supplied or not. Use
|
||||
@ -176,6 +178,7 @@ class IOAttrs:
|
||||
whole_minutes: bool = False
|
||||
soft_default: Any = MISSING
|
||||
soft_default_factory: Callable[[], Any] | _MissingType = MISSING
|
||||
enum_fallback: Enum | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -187,6 +190,7 @@ class IOAttrs:
|
||||
whole_minutes: bool = whole_minutes,
|
||||
soft_default: Any = MISSING,
|
||||
soft_default_factory: Callable[[], Any] | _MissingType = MISSING,
|
||||
enum_fallback: Enum | None = None,
|
||||
):
|
||||
# Only store values that differ from class defaults to keep
|
||||
# our instances nice and lean.
|
||||
@ -216,6 +220,8 @@ class IOAttrs:
|
||||
raise ValueError(
|
||||
'Cannot set both soft_default and soft_default_factory'
|
||||
)
|
||||
if enum_fallback is not cls.enum_fallback:
|
||||
self.enum_fallback = enum_fallback
|
||||
|
||||
def validate_for_field(self, cls: type, field: dataclasses.Field) -> None:
|
||||
"""Ensure the IOAttrs instance is ok to use with the provided field."""
|
||||
|
||||
@ -190,7 +190,16 @@ class _Inputter:
|
||||
)
|
||||
|
||||
if issubclass(origin, Enum):
|
||||
return origin(value)
|
||||
try:
|
||||
return origin(value)
|
||||
except ValueError:
|
||||
# If a fallback enum was provided in ioattrs, return
|
||||
# that for unrecognized values.
|
||||
if ioattrs is not None and ioattrs.enum_fallback is not None:
|
||||
assert type(ioattrs.enum_fallback) is origin
|
||||
return ioattrs.enum_fallback
|
||||
# Otherwise the error stands.
|
||||
raise
|
||||
|
||||
if issubclass(origin, datetime.datetime):
|
||||
return self._datetime_from_input(cls, fieldpath, value, ioattrs)
|
||||
|
||||
@ -362,7 +362,7 @@ class PrepSession:
|
||||
pass
|
||||
elif issubclass(childtypes[0], Enum):
|
||||
# Allow our usual str or int enum types as keys.
|
||||
self.prep_enum(childtypes[0])
|
||||
self.prep_enum(childtypes[0], ioattrs=None)
|
||||
else:
|
||||
raise TypeError(
|
||||
f'Dict key type {childtypes[0]} for \'{attrname}\''
|
||||
@ -412,7 +412,7 @@ class PrepSession:
|
||||
return
|
||||
|
||||
if issubclass(origin, Enum):
|
||||
self.prep_enum(origin)
|
||||
self.prep_enum(origin, ioattrs=ioattrs)
|
||||
return
|
||||
|
||||
# We allow datetime objects (and google's extended subclass of
|
||||
@ -462,7 +462,11 @@ class PrepSession:
|
||||
recursion_level=recursion_level + 1,
|
||||
)
|
||||
|
||||
def prep_enum(self, enumtype: type[Enum]) -> None:
|
||||
def prep_enum(
|
||||
self,
|
||||
enumtype: type[Enum],
|
||||
ioattrs: IOAttrs | None,
|
||||
) -> None:
|
||||
"""Run prep on an enum type."""
|
||||
|
||||
valtype: Any = None
|
||||
@ -485,3 +489,13 @@ class PrepSession:
|
||||
f' value types; dataclassio requires'
|
||||
f' them to be uniform.'
|
||||
)
|
||||
|
||||
if ioattrs is not None:
|
||||
# If they provided a fallback enum value, make sure it
|
||||
# is the correct type.
|
||||
if ioattrs.enum_fallback is not None:
|
||||
if type(ioattrs.enum_fallback) is not enumtype:
|
||||
raise TypeError(
|
||||
f'enum_fallback {ioattrs.enum_fallback} does not'
|
||||
f' match the field type ({enumtype}.'
|
||||
)
|
||||
|
||||
@ -428,7 +428,7 @@ class LogHandler(logging.Handler):
|
||||
partial(
|
||||
logging.warning,
|
||||
'efro.logging.LogHandler emit took too long'
|
||||
' (%.2fs total; %.2fs format, %.2fs echo,'
|
||||
' (%.3fs total; %.3fs format, %.3fs echo,'
|
||||
' fast_path=%s).',
|
||||
duration,
|
||||
format_duration,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user