mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-27 17:33:13 +08:00
v1.6.7
This commit is contained in:
parent
67649a0eae
commit
4d31d59467
@ -3966,50 +3966,50 @@
|
||||
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
||||
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/72/82/86956fae909ac2fe2a1abd84a361",
|
||||
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e0/06/60b8a1d7a19049a169c2b68cf89e",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/15/fe/006539c400523428bb45ce4da9b2",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/74/1d/fc9e33e565475daaac80da5252f0",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ff/56/a8f14d13ba81f23800568720624d",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/39/11/72a9a331f96f4bca715374dbf242",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/00/a8/92d6a00c4af3c43e0af4bbe7b143",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/6f/40639d4d24908fbd9c32dd781818",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/60/8f/996fdf4a1f1e26b566b5e0b4f54b",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/bc/75/418515fb999d524564ba485e3643",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/7b/ac1a200be0f37078af0991faca3b",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1a/83/3884b71fc3c83a597114c661fc94",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/be/98/c29dc5249a7d772f778de4ca4119",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c8/2f/07bae56da30dd2a4eb549d21788a",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7d/fd/cf88baa3715276b66ffc8e500c28",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/86/75a814002acbd4e3e7ce72135a69",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/9a/90efe486fbe7015fc45d71215350",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/34/81/45129983739ba3ab4c3b42aa6039",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/5e/10/d0a79286b205c0da36f0a05b289c",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/9e/2798a0a74cf535e1cccb4e2feb00",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/95/55/eefd6a0c57c7a1f5d84128d3c1de",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f2/9f/d5b12184218d6f15743d22e34ecf",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/34/b9/0da9f3ac3d89cf144130ed5d6032",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/bb/a3/8ee9ae4cd5260306bb14a17792ea",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/79/79/5b30b573a258262cf7ab5247b05d",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a3/d7/80ac73303ca83f1768b9ce1c2078",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f5/2a/c618f4f733d32d8a583348042e20",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/6a/d187d352844a3b7c1861e79d2c8f",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bb/52/ae0ed2e83a449865252d90c2ac9a",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/be/2e/4bc4bf1ec4db1b98c7d7081095d4",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/59/7e/253051aa3d105e94235f74a0b5ae",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d3/65/63cf06b0f8bd7fbc15edaf8a842b",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/61/7ff8074fa76ae8b5010b07d4f1f2",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/31/9b542804ba449ce4c8c6eddf3971",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/38/6be5cd846d6bd38acea70e8ee667",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2b/d9/7c24f63dd8737ca4ecb77d103560",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/70/2c/1d44685db575e201542685f41f32",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/82/97/2afbffeaf0989dfd5094a48ecac7",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d4/2c/d5cdded5a9d1018dad9f8e8a812e",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ac/47/16da91f514af302c5435d060d75c",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/67/5b/47db352ef527391482b42e1072e8",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/9f/1d/3d2c2056c491fdab1055a6a5f114",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/1e/a2/ff5dd453f396fa58362e24b34b5f",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/30/8b/3a547ac0ce167a0c43492dab5104",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/73/8d/d25e325791ac3e0aab48f204bea3",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/cd/a4/925f8c8ef4026b07b55ef841e8e8",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/62/b9/3539b66b55f11851633a120b40a1",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/64/d6/57d647611e82b6b36b0c2decd826",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/f7/2f/f34dd67c2d40a8988b999cb59d2e",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/f2/6c/5a0a4695dcc2a11e7941b8777e80",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/47/d4/1d346b91ebcaa1215954d9a2bb71",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b0/c3/7c1cb5a0f96212ceafc08f71369a",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5b/c9/3397972da39e948b812bc809e0c1",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/32/51/ff535d9b0f91f0defc7f9aee2bc4",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/01/ecda0f5771d5008da48e7328da34",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6c/6d/93aa177977ed7d0c529f4bcaa212",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/3d/d25cb717747c1f41a64c3dc2b353",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/3a/64a33f5f837c435fd61bc34621de",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/0f/157782b569ef6dfbe23e5f435aeb",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a6/f6/f1a09d846273291efcdebc32384c",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e1/23/154b39f6ce4017f694b9bc50b4e5",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/19/42/ffe1fbc5277708393c96ae1556b2",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/c2/81/1445b40155e83482fe8629fc4659",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/09/88/cc1f4f48e2c3f98bd9140507aca4",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/95/51/acefa044dba77f3adada7115f487",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ca/18/846e182605d341b8225865c8646c",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/71/61/f81984085eb141f9ea1e6abca272",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f0/3c/2820206454ed96ae7317eb3ff31f",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/52/68/e27ec9c0b2253213dd24cd49701c",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/83/f5/9c21b5431f1f22bad111b0872301",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/80/82/6af017a24c9de0e67d1702b538e6",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ad/00/be95b2212c2b2555c4c5130a0cd0",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/e2/5c704673f29a89445eee67248f84",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cc/1c/91c9f1ddf9c159f63a45175596eb",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/36/3b/250d4aaaccfcb8d60c4fbf3f083d",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/2b/59466f90e18c090c8a9154b38b7e",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d7/6d/f22ddf7ee877b50c3010506bff49",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d9/b2/34b5f247b3952323e4ffde3d4a2d",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ad/fb/5ccdb9da44706867aa56cb0c6316",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d9/02/36ab4592b9a994e9be386c79d2df",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/af/1a/85c3b9a25c3cabe0cf05892b34ee",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/3e/6a/a12f9aad3dc9de538d7f99751709",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/01/c5/fe63c070acfd6592a41a0a1f6480",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/2d/0a/9594864542e84204caba36de407c",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ae/42/17489667000e866dfa319d84cb04",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/1a/c4/a5ad68b0e99db6f802b7bdb3cb04",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e2/70/bff2f5ab3526af5a39e6ac4a65cd",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/3b/7c/d9e5ae045c3347812a30f75273e5",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c5/18/29d9fe8e483ce222d3263336f7e6",
|
||||
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/65/ac/d5c4162a71028c1bfa73ebc1f881"
|
||||
}
|
||||
4
.idea/dictionaries/ericf.xml
generated
4
.idea/dictionaries/ericf.xml
generated
@ -524,6 +524,7 @@
|
||||
<w>dbpath</w>
|
||||
<w>dcioexattrs</w>
|
||||
<w>dcioprep</w>
|
||||
<w>dcioprepsession</w>
|
||||
<w>dcls</w>
|
||||
<w>dcmake</w>
|
||||
<w>deathmatch</w>
|
||||
@ -1086,6 +1087,7 @@
|
||||
<w>incmd</w>
|
||||
<w>incr</w>
|
||||
<w>incrementbuild</w>
|
||||
<w>indata</w>
|
||||
<w>indentfilter</w>
|
||||
<w>indentstr</w>
|
||||
<w>indexfile</w>
|
||||
@ -1966,6 +1968,7 @@
|
||||
<w>rsdr</w>
|
||||
<w>rsms</w>
|
||||
<w>rstr</w>
|
||||
<w>rtest</w>
|
||||
<w>rtnetlink</w>
|
||||
<w>rtxt</w>
|
||||
<w>rtypes</w>
|
||||
@ -2284,6 +2287,7 @@
|
||||
<w>tcall</w>
|
||||
<w>tchar</w>
|
||||
<w>tclass</w>
|
||||
<w>tcls</w>
|
||||
<w>tcombine</w>
|
||||
<w>tdelay</w>
|
||||
<w>tdval</w>
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
### 1.6.7 (20394)
|
||||
- Fixed a vulnerability which could expose device-account uuids.
|
||||
|
||||
### 1.6.6 (20394)
|
||||
- Beginning work on moving to new asset system.
|
||||
- Added Tamil language (Thanks Ryan!)
|
||||
|
||||
@ -1 +1 @@
|
||||
128243803030405974762870876563323013389
|
||||
142084453862763162635228312125641718455
|
||||
@ -119,6 +119,11 @@ def gear_vr_controller_warning() -> None:
|
||||
color=(1, 0, 0))
|
||||
|
||||
|
||||
def uuid_str() -> str:
|
||||
import uuid
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def orientation_reset_cb_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(
|
||||
|
||||
@ -248,6 +248,7 @@
|
||||
<w>dbias</w>
|
||||
<w>dcioexattrs</w>
|
||||
<w>dcioprep</w>
|
||||
<w>dcioprepsession</w>
|
||||
<w>dcol</w>
|
||||
<w>ddcaps</w>
|
||||
<w>ddpf</w>
|
||||
@ -513,6 +514,7 @@
|
||||
<w>imagewidget</w>
|
||||
<w>importlines</w>
|
||||
<w>incentivized</w>
|
||||
<w>indata</w>
|
||||
<w>inet</w>
|
||||
<w>infotxt</w>
|
||||
<w>inides</w>
|
||||
@ -935,6 +937,7 @@
|
||||
<w>rresult</w>
|
||||
<w>rscode</w>
|
||||
<w>rsgc</w>
|
||||
<w>rtest</w>
|
||||
<w>rtypes</w>
|
||||
<w>rtypevar</w>
|
||||
<w>runnables</w>
|
||||
@ -1073,6 +1076,7 @@
|
||||
<w>tabtypes</w>
|
||||
<w>talloc</w>
|
||||
<w>targs</w>
|
||||
<w>tcls</w>
|
||||
<w>tegra</w>
|
||||
<w>telefonaktiebolaget</w>
|
||||
<w>teleported</w>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2021-12-22 for Ballistica version 1.6.6 build 20416</em></h4>
|
||||
<h4><em>last updated on 2022-01-25 for Ballistica version 1.6.7 build 20427</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>
|
||||
|
||||
@ -54,7 +54,7 @@ class AppGlobals {
|
||||
bool reset_vr_orientation{};
|
||||
bool user_ran_commands{};
|
||||
UIScale ui_scale{UIScale::kLarge};
|
||||
AccountType account_type{AccountType::kInvalid};
|
||||
V1AccountType account_type{V1AccountType::kInvalid};
|
||||
bool remote_server_accepting_connections{true};
|
||||
std::string exec_command;
|
||||
std::string user_agent_string{"BA_USER_AGENT_UNSET (" BA_PLATFORM_STRING ")"};
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kAppBuildNumber = 20416;
|
||||
const char* kAppVersion = "1.6.6";
|
||||
const int kAppBuildNumber = 20427;
|
||||
const char* kAppVersion = "1.6.7";
|
||||
|
||||
// Our standalone globals.
|
||||
// These are separated out for easy access.
|
||||
@ -213,13 +213,24 @@ auto FatalError(const std::string& message) -> void {
|
||||
auto GetUniqueSessionIdentifier() -> const std::string& {
|
||||
static std::string session_id;
|
||||
static bool have_session_id = false;
|
||||
|
||||
if (!have_session_id) {
|
||||
srand(static_cast<unsigned int>(
|
||||
Platform::GetCurrentMilliseconds())); // NOLINT
|
||||
auto tval = static_cast<uint32_t>(rand()); // NOLINT
|
||||
assert(g_platform);
|
||||
session_id = g_platform->GetUniqueDeviceIdentifier() + std::to_string(tval);
|
||||
have_session_id = true;
|
||||
if (g_python) {
|
||||
Python::ScopedInterpreterLock gil;
|
||||
auto uuid = g_python->obj(Python::ObjID::kUUIDStrCall).Call();
|
||||
if (uuid.exists()) {
|
||||
session_id = uuid.ValueAsString().c_str();
|
||||
have_session_id = true;
|
||||
}
|
||||
}
|
||||
if (!have_session_id) {
|
||||
// As an emergency fallback simply use a single random number.
|
||||
Log("WARNING: GetUniqueSessionIdentifier() using rand fallback.");
|
||||
srand(static_cast<unsigned int>(
|
||||
Platform::GetCurrentMilliseconds())); // NOLINT
|
||||
session_id = std::to_string(static_cast<uint32_t>(rand())); // NOLINT
|
||||
have_session_id = true;
|
||||
}
|
||||
if (session_id.size() >= 100) {
|
||||
Log("WARNING: session id longer than it should be.");
|
||||
}
|
||||
|
||||
@ -852,7 +852,7 @@ enum class NodeMessageType {
|
||||
kFooting
|
||||
};
|
||||
|
||||
enum class LoginState { kSignedOut, kSigningIn, kSignedIn };
|
||||
enum class V1LoginState { kSignedOut, kSigningIn, kSignedIn };
|
||||
|
||||
enum class CameraMode { kFollow, kOrbit };
|
||||
|
||||
@ -1007,7 +1007,7 @@ enum class ThreadIdentifier {
|
||||
kBGDynamics
|
||||
};
|
||||
|
||||
enum class AccountType {
|
||||
enum class V1AccountType {
|
||||
kInvalid,
|
||||
kTest,
|
||||
kGameCenter,
|
||||
|
||||
@ -16,9 +16,9 @@ namespace ballistica {
|
||||
class Account {
|
||||
public:
|
||||
Account();
|
||||
static auto AccountTypeFromString(const std::string& val) -> AccountType;
|
||||
static auto AccountTypeToString(AccountType type) -> std::string;
|
||||
static auto AccountTypeToIconString(AccountType type) -> std::string;
|
||||
static auto AccountTypeFromString(const std::string& val) -> V1AccountType;
|
||||
static auto AccountTypeToString(V1AccountType type) -> std::string;
|
||||
static auto AccountTypeToIconString(V1AccountType type) -> std::string;
|
||||
|
||||
auto GetLoginName() -> std::string;
|
||||
auto GetLoginID() -> std::string;
|
||||
@ -28,7 +28,7 @@ class Account {
|
||||
|
||||
/// Return the current account state.
|
||||
/// If an int pointer is passed, state-num will also be returned.
|
||||
auto GetLoginState(int* state_num = nullptr) -> LoginState;
|
||||
auto GetLoginState(int* state_num = nullptr) -> V1LoginState;
|
||||
|
||||
// An extra value included when passing our account info to the server
|
||||
// ...(can be used for platform-specific install-signature stuff, etc.).
|
||||
@ -37,7 +37,7 @@ class Account {
|
||||
auto SetToken(const std::string& account_id, const std::string& token)
|
||||
-> void;
|
||||
|
||||
auto SetLogin(AccountType account_type, LoginState login_state,
|
||||
auto SetLogin(V1AccountType account_type, V1LoginState login_state,
|
||||
const std::string& login_name, const std::string& login_id)
|
||||
-> void;
|
||||
|
||||
@ -57,7 +57,7 @@ class Account {
|
||||
std::string token_;
|
||||
std::string extra_;
|
||||
std::string extra_2_;
|
||||
LoginState login_state_{LoginState::kSignedOut};
|
||||
V1LoginState login_state_{V1LoginState::kSignedOut};
|
||||
int login_state_num_{};
|
||||
};
|
||||
|
||||
|
||||
@ -253,14 +253,10 @@ void Game::PushMediaPruneCall(int level) {
|
||||
});
|
||||
}
|
||||
|
||||
void Game::PushSetAccountTokenCall(const std::string& account_id,
|
||||
const std::string& token) {
|
||||
PushCall([account_id, token] { g_account->SetToken(account_id, token); });
|
||||
}
|
||||
|
||||
void Game::PushSetLoginCall(AccountType account_type, LoginState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) {
|
||||
void Game::PushSetV1LoginCall(V1AccountType account_type,
|
||||
V1LoginState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) {
|
||||
PushCall([this, account_type, account_state, account_name, account_id] {
|
||||
g_account->SetLogin(account_type, account_state, account_name, account_id);
|
||||
});
|
||||
@ -964,7 +960,6 @@ void Game::PushInterruptSignalCall() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Just go through _ba.quit()
|
||||
// FIXME: Shouldn't need to go out to the Python layer here...
|
||||
g_python->obj(Python::ObjID::kQuitCall).Call();
|
||||
});
|
||||
@ -979,11 +974,10 @@ void Game::PushAskUserForTelnetAccessCall() {
|
||||
}
|
||||
|
||||
void Game::HandleThreadPause() {
|
||||
// Give userspace python stuff a chance to pause.
|
||||
ScopedSetContext cp(GetUIContextTarget());
|
||||
g_python->obj(Python::ObjID::kOnAppPauseCall).Call();
|
||||
|
||||
// Tell our account client to commit any outstanding changes to disk.
|
||||
// Let Python and internal layers do their thing.
|
||||
g_python->obj(Python::ObjID::kOnAppPauseCall).Call();
|
||||
AppInternalOnGameThreadPause();
|
||||
}
|
||||
|
||||
@ -1831,23 +1825,24 @@ void Game::CleanUpBeforeConnectingToHost() {
|
||||
SetPublicPartyEnabled(false);
|
||||
}
|
||||
|
||||
void Game::PushPartyInviteCall(const std::string& name,
|
||||
const std::string& invite_id) {
|
||||
PushCall([this, name, invite_id] { PartyInvite(name, invite_id); });
|
||||
void Game::PushV1PartyInviteCall(const std::string& name,
|
||||
const std::string& invite_id) {
|
||||
PushCall([this, name, invite_id] { V1PartyInvite(name, invite_id); });
|
||||
}
|
||||
|
||||
void Game::PartyInvite(const std::string& name, const std::string& invite_id) {
|
||||
void Game::V1PartyInvite(const std::string& name,
|
||||
const std::string& invite_id) {
|
||||
assert(InGameThread());
|
||||
g_python->PartyInvite(name, invite_id);
|
||||
g_python->V1PartyInvite(name, invite_id);
|
||||
}
|
||||
|
||||
void Game::PushPartyInviteRevokeCall(const std::string& invite_id) {
|
||||
PushCall([this, invite_id] { PartyInviteRevoke(invite_id); });
|
||||
void Game::PushV1PartyInviteRevokeCall(const std::string& invite_id) {
|
||||
PushCall([this, invite_id] { V1PartyInviteRevoke(invite_id); });
|
||||
}
|
||||
|
||||
void Game::PartyInviteRevoke(const std::string& invite_id) {
|
||||
void Game::V1PartyInviteRevoke(const std::string& invite_id) {
|
||||
assert(InGameThread());
|
||||
g_python->PartyInviteRevoke(invite_id);
|
||||
g_python->V1PartyInviteRevoke(invite_id);
|
||||
}
|
||||
|
||||
auto Game::GetPartySize() const -> int {
|
||||
|
||||
@ -32,14 +32,13 @@ class Game : public Module {
|
||||
auto LaunchClientSession() -> void;
|
||||
auto LaunchReplaySession(const std::string& file_name) -> void;
|
||||
|
||||
auto PushSetLoginCall(AccountType account_type, LoginState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) -> void;
|
||||
auto PushSetAccountTokenCall(const std::string& account_id,
|
||||
const std::string& token) -> void;
|
||||
auto PushPartyInviteCall(const std::string& name,
|
||||
const std::string& invite_id) -> void;
|
||||
auto PushPartyInviteRevokeCall(const std::string& invite_id) -> void;
|
||||
auto PushSetV1LoginCall(V1AccountType account_type,
|
||||
V1LoginState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) -> void;
|
||||
auto PushV1PartyInviteCall(const std::string& name,
|
||||
const std::string& invite_id) -> void;
|
||||
auto PushV1PartyInviteRevokeCall(const std::string& invite_id) -> void;
|
||||
auto PushInitialScreenCreatedCall() -> void;
|
||||
auto PushApplyConfigCall() -> void;
|
||||
auto PushRemoveGraphicsServerRenderHoldCall() -> void;
|
||||
@ -253,9 +252,9 @@ class Game : public Module {
|
||||
auto HandleQuitOnIdle() -> void;
|
||||
auto InitSpecialChars() -> void;
|
||||
auto Draw() -> void;
|
||||
auto PartyInvite(const std::string& name, const std::string& invite_id)
|
||||
auto V1PartyInvite(const std::string& name, const std::string& invite_id)
|
||||
-> void;
|
||||
auto PartyInviteRevoke(const std::string& invite_id) -> void;
|
||||
auto V1PartyInviteRevoke(const std::string& invite_id) -> void;
|
||||
auto InitialScreenCreated() -> void;
|
||||
auto MainMenuPress(InputDevice* device) -> void;
|
||||
auto ScreenResize(float virtual_width, float virtual_height,
|
||||
|
||||
@ -34,7 +34,7 @@ PlayerSpec::PlayerSpec(const std::string& s) {
|
||||
Log("Error creating PlayerSpec from string: '" + s + "'");
|
||||
name_ = "<error>";
|
||||
short_name_ = "";
|
||||
account_type_ = AccountType::kInvalid;
|
||||
account_type_ = V1AccountType::kInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ auto PlayerSpec::GetSpecString() const -> std::string {
|
||||
|
||||
auto PlayerSpec::GetAccountPlayerSpec() -> PlayerSpec {
|
||||
PlayerSpec spec;
|
||||
if (g_account->GetLoginState() == LoginState::kSignedIn) {
|
||||
if (g_account->GetLoginState() == V1LoginState::kSignedIn) {
|
||||
spec.account_type_ = g_app_globals->account_type;
|
||||
spec.name_ =
|
||||
Utils::GetValidUTF8(g_account->GetLoginName().c_str(), "bsgaps");
|
||||
|
||||
@ -48,7 +48,7 @@ class PlayerSpec {
|
||||
private:
|
||||
std::string name_;
|
||||
std::string short_name_;
|
||||
AccountType account_type_{AccountType::kInvalid};
|
||||
V1AccountType account_type_{V1AccountType::kInvalid};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
@ -153,10 +153,12 @@ class Platform {
|
||||
// Return a string *reasonably* likely to be unique and consistent for this
|
||||
// device. Do not assume this is globally unique and *do not* assume that it
|
||||
// will never ever change (hardware upgrades may affect it, etc).
|
||||
// IMPORTANT: This value should NEVER be sent over the wire to peers.
|
||||
virtual auto GetUniqueDeviceIdentifier() -> const std::string&;
|
||||
|
||||
// Returns the ID to use for the device account
|
||||
auto GetDeviceAccountID() -> std::string;
|
||||
|
||||
auto GetConfigDirectory() -> std::string;
|
||||
auto GetConfigFilePath() -> std::string;
|
||||
auto GetUserPythonDirectory() -> std::string;
|
||||
|
||||
@ -2322,8 +2322,8 @@ auto Python::ObjToString(PyObject* obj) -> std::string {
|
||||
}
|
||||
}
|
||||
|
||||
void Python::PartyInvite(const std::string& player,
|
||||
const std::string& invite_id) {
|
||||
void Python::V1PartyInvite(const std::string& player,
|
||||
const std::string& invite_id) {
|
||||
ScopedSetContext cp(g_game->GetUIContext());
|
||||
PythonRef args(
|
||||
Py_BuildValue(
|
||||
@ -2336,7 +2336,7 @@ void Python::PartyInvite(const std::string& player,
|
||||
obj(ObjID::kHandlePartyInviteCall).Call(args);
|
||||
}
|
||||
|
||||
void Python::PartyInviteRevoke(const std::string& invite_id) {
|
||||
void Python::V1PartyInviteRevoke(const std::string& invite_id) {
|
||||
ScopedSetContext cp(g_game->GetUIContext());
|
||||
PythonRef args(
|
||||
Py_BuildValue("(O)", PythonRef(PyUnicode_FromString(invite_id.c_str()),
|
||||
|
||||
@ -142,8 +142,8 @@ class Python {
|
||||
/// is useful as an object identifier/etc.
|
||||
static auto GetPythonFileLocation(bool pretty = true) -> std::string;
|
||||
|
||||
void PartyInvite(const std::string& player, const std::string& invite_id);
|
||||
void PartyInviteRevoke(const std::string& invite_id);
|
||||
void V1PartyInvite(const std::string& player, const std::string& invite_id);
|
||||
void V1PartyInviteRevoke(const std::string& invite_id);
|
||||
void set_env_obj(PyObject* obj) { env_ = obj; }
|
||||
auto env_obj() const -> PyObject* {
|
||||
assert(env_);
|
||||
@ -351,6 +351,7 @@ class Python {
|
||||
kPlayerClass,
|
||||
kGetPlayerIconCall,
|
||||
kLstrFromJsonCall,
|
||||
kUUIDStrCall,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
|
||||
@ -134,4 +134,5 @@ def get_binding_values() -> tuple[Any, ...]:
|
||||
_player.Player, # kPlayerClass
|
||||
_hooks.get_player_icon, # kGetPlayerIconCall
|
||||
_language.Lstr.from_json, # kLstrFromJsonCall
|
||||
_hooks.uuid_str, # kUUIDStrCall
|
||||
) # yapf: disable
|
||||
|
||||
@ -13,8 +13,8 @@ import pytest
|
||||
|
||||
from efro.util import utc_now
|
||||
from efro.dataclassio import (dataclass_validate, dataclass_from_dict,
|
||||
dataclass_to_dict, ioprepped, IOAttrs, Codec,
|
||||
DataclassFieldLookup)
|
||||
dataclass_to_dict, ioprepped, ioprep, IOAttrs,
|
||||
Codec, DataclassFieldLookup, IOExtendedData)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
@ -220,11 +220,11 @@ def test_assign() -> None:
|
||||
dataclass_from_dict(_TestClass, {'ssval': {}})
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_from_dict(_TestClass, {'ssval': set()})
|
||||
with pytest.raises(TypeError):
|
||||
with pytest.raises(ValueError):
|
||||
dataclass_from_dict(_TestClass, {'tupleval': []})
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_from_dict(_TestClass, {'tupleval': [1, 1, 1]})
|
||||
with pytest.raises(TypeError):
|
||||
with pytest.raises(ValueError):
|
||||
dataclass_from_dict(_TestClass, {'tupleval': [2, 'foof', True, True]})
|
||||
|
||||
# Fields with type Any should accept all types which are directly
|
||||
@ -652,6 +652,36 @@ def test_name_clashes() -> None:
|
||||
ival2: Annotated[int, IOAttrs('ival')] = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class _RecursiveTest:
|
||||
val: int
|
||||
child: Optional[_RecursiveTest] = None
|
||||
|
||||
|
||||
def test_recursive() -> None:
|
||||
"""Test recursive classes."""
|
||||
|
||||
# Can't use ioprepped on this since it refers to its own name which
|
||||
# doesn't exist yet. Have to explicitly prep it after.
|
||||
ioprep(_RecursiveTest)
|
||||
|
||||
rtest = _RecursiveTest(val=1)
|
||||
rtest.child = _RecursiveTest(val=2)
|
||||
rtest.child.child = _RecursiveTest(val=3)
|
||||
expected_output = {
|
||||
'val': 1,
|
||||
'child': {
|
||||
'val': 2,
|
||||
'child': {
|
||||
'val': 3,
|
||||
'child': None
|
||||
}
|
||||
}
|
||||
}
|
||||
assert dataclass_to_dict(rtest) == expected_output
|
||||
assert dataclass_from_dict(_RecursiveTest, expected_output) == rtest
|
||||
|
||||
|
||||
def test_any() -> None:
|
||||
"""Test data included with type Any."""
|
||||
|
||||
@ -795,3 +825,51 @@ def test_nested() -> None:
|
||||
|
||||
subval: _TestSubClass = field(default_factory=_TestSubClass)
|
||||
enval: _TestEnum = _TestEnum.VAL1
|
||||
|
||||
|
||||
def test_extended_data() -> None:
|
||||
"""Test IOExtendedData functionality."""
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass:
|
||||
vals: tuple[int, int]
|
||||
|
||||
# This data lines up.
|
||||
indata = {'vals': [0, 0]}
|
||||
_obj = dataclass_from_dict(_TestClass, indata)
|
||||
|
||||
# This data doesn't.
|
||||
indata = {'vals': [0, 0, 0]}
|
||||
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.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClass2(IOExtendedData):
|
||||
vals: tuple[int, int]
|
||||
|
||||
@classmethod
|
||||
def will_input(cls, data: dict) -> None:
|
||||
data['vals'] = data['vals'][:2]
|
||||
|
||||
def will_output(self) -> None:
|
||||
self.vals = (0, 0)
|
||||
|
||||
# This data lines up.
|
||||
indata = {'vals': [0, 0]}
|
||||
_obj2 = dataclass_from_dict(_TestClass2, indata)
|
||||
|
||||
# Now this data will too via our custom input filter.
|
||||
indata = {'vals': [0, 0, 0]}
|
||||
_obj2 = dataclass_from_dict(_TestClass2, indata)
|
||||
|
||||
# Ok, now test output:
|
||||
|
||||
# Does the expected thing.
|
||||
assert dataclass_to_dict(_TestClass(vals=(1, 2))) == {'vals': [1, 2]}
|
||||
|
||||
# Uses our output filter.
|
||||
assert dataclass_to_dict(_TestClass2(vals=(1, 2))) == {'vals': [0, 0]}
|
||||
|
||||
@ -38,8 +38,8 @@ class AssetType(Enum):
|
||||
@dataclass
|
||||
class AssetPackageFlavorManifest:
|
||||
"""A manifest of asset info for a specific flavor of an asset package."""
|
||||
assetfiles: Annotated[dict[str, str],
|
||||
IOAttrs('assetfiles')] = field(default_factory=dict)
|
||||
cloudfiles: Annotated[dict[str, str],
|
||||
IOAttrs('cloudfiles')] = field(default_factory=dict)
|
||||
|
||||
|
||||
@ioprepped
|
||||
|
||||
@ -15,17 +15,19 @@ 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 ioprep, ioprepped, is_ioprepped_dataclass
|
||||
from efro.dataclassio._base import Codec, IOAttrs, IOExtendedData
|
||||
from efro.dataclassio._prep import (ioprep, ioprepped, will_ioprep,
|
||||
is_ioprepped_dataclass)
|
||||
from efro.dataclassio._pathcapture import DataclassFieldLookup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
__all__ = [
|
||||
'Codec', 'IOAttrs', 'ioprep', 'ioprepped', 'is_ioprepped_dataclass',
|
||||
'DataclassFieldLookup', 'dataclass_to_dict', 'dataclass_to_json',
|
||||
'dataclass_from_dict', 'dataclass_from_json', 'dataclass_validate'
|
||||
'Codec', 'IOAttrs', 'IOExtendedData', 'ioprep', 'ioprepped', 'will_ioprep',
|
||||
'is_ioprepped_dataclass', 'DataclassFieldLookup', 'dataclass_to_dict',
|
||||
'dataclass_to_json', 'dataclass_from_dict', 'dataclass_from_json',
|
||||
'dataclass_validate'
|
||||
]
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
@ -12,15 +12,6 @@ from typing import TYPE_CHECKING, get_args
|
||||
# noinspection PyProtectedMember
|
||||
from typing import _AnnotatedAlias # type: ignore
|
||||
|
||||
_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:
|
||||
from typing import Any, Optional
|
||||
|
||||
@ -32,14 +23,6 @@ SIMPLE_TYPES = {int, bool, str, float, type(None)}
|
||||
EXTRA_ATTRS_ATTR = '_DCIOEXATTRS'
|
||||
|
||||
|
||||
def _ensure_datetime_is_timezone_aware(value: datetime.datetime) -> None:
|
||||
# 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')
|
||||
|
||||
|
||||
def _raise_type_error(fieldpath: str, valuetype: type,
|
||||
expected: tuple[type, ...]) -> None:
|
||||
"""Raise an error when a field value's type does not match expected."""
|
||||
@ -67,6 +50,24 @@ class Codec(Enum):
|
||||
FIRESTORE = 'firestore'
|
||||
|
||||
|
||||
class IOExtendedData:
|
||||
"""A class that data types can inherit from for extra functionality."""
|
||||
|
||||
def will_output(self) -> None:
|
||||
"""Called before data is sent to an outputter.
|
||||
|
||||
Can be overridden to validate or filter data before
|
||||
sending it on its way.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def will_input(cls, data: dict) -> None:
|
||||
"""Called on raw data before a class instance is created from it.
|
||||
|
||||
Can be overridden to migrate old data formats to new, etc.
|
||||
"""
|
||||
|
||||
|
||||
def _is_valid_for_codec(obj: Any, codec: Codec) -> bool:
|
||||
"""Return whether a value consists solely of json-supported types.
|
||||
|
||||
|
||||
@ -14,11 +14,11 @@ import typing
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Generic, TypeVar
|
||||
|
||||
from efro.util import enum_by_value
|
||||
from efro.util import enum_by_value, check_utc
|
||||
from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR,
|
||||
_is_valid_for_codec, _get_origin,
|
||||
SIMPLE_TYPES, _raise_type_error,
|
||||
_ensure_datetime_is_timezone_aware)
|
||||
IOExtendedData)
|
||||
from efro.dataclassio._prep import PrepSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -48,6 +48,12 @@ class _Inputter(Generic[T]):
|
||||
|
||||
def run(self, values: dict) -> T:
|
||||
"""Do the thing."""
|
||||
|
||||
# For special extended data types, call their 'will_output' callback.
|
||||
tcls = self._cls
|
||||
if issubclass(tcls, IOExtendedData):
|
||||
tcls.will_input(values)
|
||||
|
||||
out = self._dataclass_from_input(self._cls, '', values)
|
||||
assert isinstance(out, self._cls)
|
||||
return out
|
||||
@ -159,6 +165,7 @@ class _Inputter(Generic[T]):
|
||||
|
||||
prep = PrepSession(explicit=False).prep_dataclass(cls,
|
||||
recursion_level=0)
|
||||
assert prep is not None
|
||||
|
||||
extra_attrs = {}
|
||||
|
||||
@ -344,7 +351,7 @@ class _Inputter(Generic[T]):
|
||||
f'Invalid input value for "{fieldpath}" on'
|
||||
f' "{cls.__name__}";'
|
||||
f' expected a datetime, got a {type(value).__name__}')
|
||||
_ensure_datetime_is_timezone_aware(value)
|
||||
check_utc(value)
|
||||
return value
|
||||
|
||||
assert self._codec is Codec.JSON
|
||||
@ -355,9 +362,9 @@ class _Inputter(Generic[T]):
|
||||
f'Invalid input value for "{fieldpath}" on "{cls.__name__}";'
|
||||
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(
|
||||
raise ValueError(
|
||||
f'Invalid input value for "{fieldpath}" on "{cls.__name__}";'
|
||||
f' expected a list of 7 ints.')
|
||||
f' expected a list of 7 ints, got {[type(v) for v in value]}.')
|
||||
out = datetime.datetime( # type: ignore
|
||||
*value, tzinfo=datetime.timezone.utc)
|
||||
if ioattrs is not None:
|
||||
@ -380,9 +387,9 @@ class _Inputter(Generic[T]):
|
||||
assert childanntypes
|
||||
|
||||
if len(value) != len(childanntypes):
|
||||
raise TypeError(f'Invalid tuple input for "{fieldpath}";'
|
||||
f' expected {len(childanntypes)} values,'
|
||||
f' found {len(value)}.')
|
||||
raise ValueError(f'Invalid tuple input for "{fieldpath}";'
|
||||
f' expected {len(childanntypes)} values,'
|
||||
f' found {len(value)}.')
|
||||
|
||||
for i, childanntype in enumerate(childanntypes):
|
||||
childval = value[i]
|
||||
|
||||
@ -14,10 +14,11 @@ import typing
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.util import check_utc
|
||||
from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR,
|
||||
_is_valid_for_codec, _get_origin,
|
||||
SIMPLE_TYPES, _raise_type_error,
|
||||
_ensure_datetime_is_timezone_aware)
|
||||
IOExtendedData)
|
||||
from efro.dataclassio._prep import PrepSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -37,6 +38,11 @@ class _Outputter:
|
||||
|
||||
def run(self) -> Any:
|
||||
"""Do the thing."""
|
||||
|
||||
# For special extended data types, call their 'will_output' callback.
|
||||
if isinstance(self._obj, IOExtendedData):
|
||||
self._obj.will_output()
|
||||
|
||||
return self._process_dataclass(type(self._obj), self._obj, '')
|
||||
|
||||
def _process_dataclass(self, cls: type, obj: Any, fieldpath: str) -> Any:
|
||||
@ -44,6 +50,7 @@ class _Outputter:
|
||||
# pylint: disable=too-many-branches
|
||||
prep = PrepSession(explicit=False).prep_dataclass(type(obj),
|
||||
recursion_level=0)
|
||||
assert prep is not None
|
||||
fields = dataclasses.fields(obj)
|
||||
out: Optional[dict[str, Any]] = {} if self._create else None
|
||||
for field in fields:
|
||||
@ -242,7 +249,7 @@ class _Outputter:
|
||||
if not isinstance(value, origin):
|
||||
raise TypeError(f'Expected a {origin} for {fieldpath};'
|
||||
f' found a {type(value)}.')
|
||||
_ensure_datetime_is_timezone_aware(value)
|
||||
check_utc(value)
|
||||
if ioattrs is not None:
|
||||
ioattrs.validate_datetime(value, fieldpath)
|
||||
if self._codec is Codec.FIRESTORE:
|
||||
|
||||
@ -36,6 +36,7 @@ class _PathCapture:
|
||||
|
||||
prep = PrepSession(explicit=False).prep_dataclass(self._cls,
|
||||
recursion_level=0)
|
||||
assert prep is not None
|
||||
try:
|
||||
anntype = prep.annotations[name]
|
||||
except KeyError as exc:
|
||||
|
||||
@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, TypeVar, get_type_hints
|
||||
from efro.dataclassio._base import _parse_annotated, _get_origin, SIMPLE_TYPES
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
@ -27,9 +27,13 @@ T = TypeVar('T')
|
||||
# (basically for detecting recursive types)
|
||||
MAX_RECURSION = 10
|
||||
|
||||
# Attr name for data we store on dataclass types as part of prep.
|
||||
# Attr name for data we store on dataclass types that have been prepped.
|
||||
PREP_ATTR = '_DCIOPREP'
|
||||
|
||||
# We also store the prep-session while the prep is in progress.
|
||||
# (necessary to support recursive types).
|
||||
PREP_SESSION_ATTR = '_DCIOPREPSESSION'
|
||||
|
||||
|
||||
def ioprep(cls: type) -> None:
|
||||
"""Prep a dataclass type for use with this module's functionality.
|
||||
@ -64,6 +68,23 @@ def ioprepped(cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
def will_ioprep(cls: type[T]) -> type[T]:
|
||||
"""Class decorator hinting that we will prep a class later.
|
||||
|
||||
In some cases (such as recursive types) we cannot use the @ioprepped
|
||||
decorator and must instead call ioprep() explicitly later. However,
|
||||
some of our custom pylint checking behaves differently when the
|
||||
@ioprepped decorator is present, in that case requiring type annotations
|
||||
to be present and not simply forward declared under an "if TYPE_CHECKING"
|
||||
block. (since they are used at runtime).
|
||||
|
||||
The @will_ioprep decorator triggers the same pylint behavior
|
||||
differences as @ioprepped (which are necessary for the later ioprep() call
|
||||
to work correctly) but without actually running any prep itself.
|
||||
"""
|
||||
return cls
|
||||
|
||||
|
||||
def is_ioprepped_dataclass(obj: Any) -> bool:
|
||||
"""Return whether the obj is an ioprepped dataclass type or instance."""
|
||||
cls = obj if isinstance(obj, type) else type(obj)
|
||||
@ -90,8 +111,15 @@ class PrepSession:
|
||||
def __init__(self, explicit: bool):
|
||||
self.explicit = explicit
|
||||
|
||||
def prep_dataclass(self, cls: type, recursion_level: int) -> PrepData:
|
||||
"""Run prep on a dataclass if necessary and return its prep data."""
|
||||
def prep_dataclass(self, cls: type,
|
||||
recursion_level: int) -> Optional[PrepData]:
|
||||
"""Run prep on a dataclass if necessary and return its prep data.
|
||||
|
||||
The only case where this will return None is for recursive types
|
||||
if the type is already being prepped higher in the call order.
|
||||
"""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
# We should only need to do this once per dataclass.
|
||||
existing_data = getattr(cls, PREP_ATTR, None)
|
||||
@ -99,8 +127,9 @@ class PrepSession:
|
||||
assert isinstance(existing_data, PrepData)
|
||||
return existing_data
|
||||
|
||||
# If we run into classes containing themselves, we may have
|
||||
# to do something smarter to handle it.
|
||||
# Sanity check.
|
||||
# Note that we now support recursive types via the PREP_SESSION_ATTR,
|
||||
# so we theoretically shouldn't run into this this.
|
||||
if recursion_level > MAX_RECURSION:
|
||||
raise RuntimeError('Max recursion exceeded.')
|
||||
|
||||
@ -108,6 +137,18 @@ class PrepSession:
|
||||
if not isinstance(cls, type) or not dataclasses.is_dataclass(cls):
|
||||
raise TypeError(f'Passed arg {cls} is not a dataclass type.')
|
||||
|
||||
# Add a pointer to the prep-session while doing the prep.
|
||||
# This way we can ignore types that we're already in the process
|
||||
# of prepping and can support recursive types.
|
||||
existing_prep = getattr(cls, PREP_SESSION_ATTR, None)
|
||||
if existing_prep is not None:
|
||||
if existing_prep is self:
|
||||
return None
|
||||
# We shouldn't need to support failed preps
|
||||
# or preps from multiple threads at once.
|
||||
raise RuntimeError('Found existing in-progress prep.')
|
||||
setattr(cls, PREP_SESSION_ATTR, self)
|
||||
|
||||
# Generate a warning on non-explicit preps; we prefer prep to
|
||||
# happen explicitly at runtime so errors can be detected early on.
|
||||
if not self.explicit:
|
||||
@ -126,7 +167,6 @@ class PrepSession:
|
||||
include_extras=True)
|
||||
# pylint: enable=unexpected-keyword-arg
|
||||
except Exception as exc:
|
||||
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'
|
||||
@ -175,6 +215,10 @@ class PrepSession:
|
||||
annotations=resolved_annotations,
|
||||
storage_names_to_attr_names=storage_names_to_attr_names)
|
||||
setattr(cls, PREP_ATTR, prepdata)
|
||||
|
||||
# Clear our prep-session tag.
|
||||
assert getattr(cls, PREP_SESSION_ATTR, None) is self
|
||||
delattr(cls, PREP_SESSION_ATTR)
|
||||
return prepdata
|
||||
|
||||
def prep_type(self, cls: type, attrname: str, anntype: Any,
|
||||
|
||||
@ -11,6 +11,15 @@ import functools
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, cast, TypeVar, Generic
|
||||
|
||||
_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:
|
||||
import asyncio
|
||||
from efro.call import Call as Call # 'as Call' so we re-export.
|
||||
@ -62,6 +71,14 @@ def enum_by_value(cls: type[TENUM], value: Any) -> TENUM:
|
||||
(value, cls.__name__)) from None
|
||||
|
||||
|
||||
def check_utc(value: datetime.datetime) -> None:
|
||||
"""Ensure a datetime value is timezone-aware utc."""
|
||||
if (value.tzinfo is not datetime.timezone.utc
|
||||
and (_pytz_utc is None or value.tzinfo is not _pytz_utc)):
|
||||
raise ValueError('datetime value does not have timezone set as'
|
||||
' datetime.timezone.utc')
|
||||
|
||||
|
||||
def utc_now() -> datetime.datetime:
|
||||
"""Get offset-aware current utc time.
|
||||
|
||||
@ -240,7 +257,8 @@ class DispatchMethodWrapper(Generic[TARG, TRET]):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def register(func: Callable[[Any, Any], TRET]) -> Callable:
|
||||
def register(
|
||||
func: Callable[[Any, Any], TRET]) -> Callable[[Any, Any], TRET]:
|
||||
"""Register a new dispatch handler for this dispatch-method."""
|
||||
|
||||
registry: dict[Any, Callable]
|
||||
@ -312,12 +330,16 @@ class ValueDispatcher(Generic[TVAL, TRET]):
|
||||
return handler()
|
||||
return self._base_call(value)
|
||||
|
||||
def _add_handler(self, value: TVAL, call: Callable[[], TRET]) -> None:
|
||||
def _add_handler(self, value: TVAL,
|
||||
call: Callable[[], TRET]) -> Callable[[], TRET]:
|
||||
if value in self._handlers:
|
||||
raise RuntimeError(f'Duplicate handlers added for {value}')
|
||||
self._handlers[value] = call
|
||||
return call
|
||||
|
||||
def register(self, value: TVAL) -> Callable[[Callable[[], TRET]], None]:
|
||||
def register(
|
||||
self,
|
||||
value: TVAL) -> Callable[[Callable[[], TRET]], Callable[[], TRET]]:
|
||||
"""Add a handler to the dispatcher."""
|
||||
from functools import partial
|
||||
return partial(self._add_handler, value)
|
||||
@ -343,13 +365,16 @@ class ValueDispatcher1Arg(Generic[TVAL, TARG, TRET]):
|
||||
return handler(arg)
|
||||
return self._base_call(value, arg)
|
||||
|
||||
def _add_handler(self, value: TVAL, call: Callable[[TARG], TRET]) -> None:
|
||||
def _add_handler(self, value: TVAL,
|
||||
call: Callable[[TARG], TRET]) -> Callable[[TARG], TRET]:
|
||||
if value in self._handlers:
|
||||
raise RuntimeError(f'Duplicate handlers added for {value}')
|
||||
self._handlers[value] = call
|
||||
return call
|
||||
|
||||
def register(self,
|
||||
value: TVAL) -> Callable[[Callable[[TARG], TRET]], None]:
|
||||
def register(
|
||||
self, value: TVAL
|
||||
) -> Callable[[Callable[[TARG], TRET]], Callable[[TARG], TRET]]:
|
||||
"""Add a handler to the dispatcher."""
|
||||
from functools import partial
|
||||
return partial(self._add_handler, value)
|
||||
@ -363,8 +388,9 @@ if TYPE_CHECKING:
|
||||
def __call__(self, value: TVAL) -> TRET:
|
||||
...
|
||||
|
||||
def register(self,
|
||||
value: TVAL) -> Callable[[Callable[[TSELF], TRET]], None]:
|
||||
def register(
|
||||
self, value: TVAL
|
||||
) -> Callable[[Callable[[TSELF], TRET]], Callable[[TSELF], TRET]]:
|
||||
"""Add a handler to the dispatcher."""
|
||||
...
|
||||
|
||||
|
||||
@ -181,8 +181,8 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
|
||||
for dec in fnode.decorators.nodes:
|
||||
|
||||
# Look for dataclassio.ioprepped.
|
||||
if (isinstance(dec, astroid.nodes.Attribute)
|
||||
and dec.attrname == 'ioprepped'
|
||||
if (isinstance(dec, astroid.nodes.Attribute) and
|
||||
dec.attrname in {'ioprepped', 'will_ioprep'}
|
||||
and isinstance(dec.expr, astroid.nodes.Name)
|
||||
and dec.expr.name == 'dataclassio'):
|
||||
found_ioprepped = True
|
||||
@ -190,7 +190,7 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
|
||||
|
||||
# Look for simply 'ioprepped'.
|
||||
if (isinstance(dec, astroid.nodes.Name)
|
||||
and dec.name == 'ioprepped'):
|
||||
and dec.name in {'ioprepped', 'will_ioprep'}):
|
||||
found_ioprepped = True
|
||||
break
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user