mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +08:00
better soft_default type checking
This commit is contained in:
parent
9529c7cf32
commit
87460145db
@ -3971,26 +3971,26 @@
|
||||
"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/b2/e5/0ee0561e16257a32830645239f34",
|
||||
"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/3a/d3/38075453348d9d2abd102554a937",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/1c/1cd7a078b292c0d01d634f97f222",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fa/20/4f69c5c8a3e9bf9afabff8a7118f",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3a/3a/d23f5fabe309b6e8694aff471b9b",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4d/6f/12942dd120a3ec0963249b596989",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4d/90/3b11a947cd2befe576c12dc58a9c",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/77/77/2c01655f3144cd40c9ee252c7226",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f2/62/a0ba340093f8c143ab39149244e9",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/67/a9/4b48db661bc7b1f6338ee0389e87",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8e/44/c087c45ca09e5a8e4284076d4663",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d8/ee/452c5c9164807e82d97214f7fc5b",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7a/f0/2a4003966c6e7ec531bcfb19f013",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ec/be/637f05ea13de731126047d88a6c2",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/53/c6/2503d6a33f1a86d3504732091540",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/14/ba5b7d47bec90c93af50fe344a1e",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/79/e08d502d6b980db9c9506b6614ca",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/62/a7/c88cf55d6242a7b28467b193cb7f",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/36/7e/6f55132fea94bbca6b41cc764a28",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/f4/4a/a7aa2270ca1bd8cc15cb5204d089",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/86/df/dd3bef3462b735d85243e24540f9",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5e/57/faba903f766084e7e4e5b6eebb73",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/05/95/792c416c8a4a399f4ad8a90cda06",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/68/606372da26157d45e396eda4a6fc",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/12/00/87f70c8158a37abbbfb6e9226aa4",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f3/52/700948881e3ebcacebc842eb8e25",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/11/f1/e4eb196828af3b9b53bfbc8c1b14",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6d/75/b8a6efdc2a5a8d0aa45a151c133c",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5b/9e/b4f1b05d0af9389501057b52141b",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e7/bf/7ce1b7cb02b9589a3d3aa24fb309",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fa/39/3affaf5911a076b45d51d33b9068",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/03/72/5bd95cc26e648c9a53ded3e7c3f7",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/a4/4bc45cda2a7777e7f9738895e4bb",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/01/1c/a1973631978d7b7a7d7f85ed6e7c",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e0/1c/c1f316bb1334145f691686c804b6",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7f/81/6543391def25718bf9ed0da8b9f3",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9b/06/5e1bd19506b1806f793637b73325",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1c/9b/994c9aff7a533da605d3c90bb93f",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/dd/41/fd523388e077ca84d37a4fd912ad",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/4b/9f/2f280864e003e275a4f7f324c4ee",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/0b/a7/9c93f87f6e2430e1af483b2269ef",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f9/81/e339e45d6c650df8217acbfb5f29",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/de/d8/ce0ae67d8fa1bc4afb931c2e8a0f",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7b/0a/4907ea64f8492164a84ab9503aaf",
|
||||
|
||||
1
.idea/dictionaries/ericf.xml
generated
1
.idea/dictionaries/ericf.xml
generated
@ -2214,6 +2214,7 @@
|
||||
<w>sockaddr</w>
|
||||
<w>socketmodule</w>
|
||||
<w>socketserver</w>
|
||||
<w>softdefault</w>
|
||||
<w>somevar</w>
|
||||
<w>sortname</w>
|
||||
<w>soundtrackname</w>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
### 1.6.12 (20561, 2022-04-21)
|
||||
### 1.6.12 (20563, 2022-04-21)
|
||||
- More internal work on V2 master-server communication
|
||||
|
||||
### 1.6.11 (20539, 2022-03-23)
|
||||
|
||||
@ -1102,6 +1102,7 @@
|
||||
<w>snorm</w>
|
||||
<w>sockaddr</w>
|
||||
<w>soffs</w>
|
||||
<w>softdefault</w>
|
||||
<w>solaris</w>
|
||||
<w>sortname</w>
|
||||
<w>sourcenode</w>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kAppBuildNumber = 20561;
|
||||
const int kAppBuildNumber = 20563;
|
||||
const char* kAppVersion = "1.6.12";
|
||||
|
||||
// Our standalone globals.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Testing dataclasses functionality."""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -877,6 +878,7 @@ def test_extended_data() -> None:
|
||||
|
||||
def test_soft_default() -> None:
|
||||
"""Test soft_default IOAttr value."""
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
# Try both of these with and without storage_name to make sure
|
||||
# soft_default interacts correctly with both cases.
|
||||
@ -946,7 +948,16 @@ def test_soft_default() -> None:
|
||||
|
||||
assert dataclass_to_dict(_TestClassC3(0)) == {}
|
||||
|
||||
# we should disallow passing a few mutable types as soft_defaults
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassC3b:
|
||||
ival: Annotated[
|
||||
int,
|
||||
IOAttrs(store_default=False, soft_default_factory=lambda: 0)]
|
||||
|
||||
assert dataclass_to_dict(_TestClassC3b(0)) == {}
|
||||
|
||||
# We disallow passing a few mutable types as soft_defaults
|
||||
# just as dataclass does with regular defaults.
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ -954,3 +965,73 @@ def test_soft_default() -> None:
|
||||
@dataclass
|
||||
class _TestClassD:
|
||||
lval: Annotated[list, IOAttrs(soft_default=[])]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassD2:
|
||||
lval: Annotated[set, IOAttrs(soft_default=set())]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
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
|
||||
# early the better)
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE:
|
||||
lval: Annotated[int, IOAttrs(soft_default='')]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE2:
|
||||
lval: Annotated[str, IOAttrs(soft_default=45)]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
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).
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE4:
|
||||
lval: Annotated[Optional[str], IOAttrs(soft_default=None)]
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE5:
|
||||
lval: Annotated[Optional[str], 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.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE6:
|
||||
lval: Annotated[tuple[int, int], IOAttrs(soft_default=('foo', 'bar'))]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_from_dict(_TestClassE6, {})
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class _TestClassE7:
|
||||
lval: Annotated[Optional[bool], IOAttrs(soft_default=12)]
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
dataclass_from_dict(_TestClassE7, {})
|
||||
|
||||
@ -163,6 +163,9 @@ class IOAttrs:
|
||||
self.soft_default = soft_default
|
||||
if soft_default_factory is not cls.soft_default_factory:
|
||||
self.soft_default_factory = soft_default_factory
|
||||
if self.soft_default is not cls.soft_default:
|
||||
raise ValueError('Cannot set both soft_default'
|
||||
' and soft_default_factory')
|
||||
|
||||
def validate_for_field(self, cls: type, field: dataclasses.Field) -> None:
|
||||
"""Ensure the IOAttrs instance is ok to use with the provided field."""
|
||||
|
||||
@ -23,7 +23,9 @@ from efro.dataclassio._prep import PrepSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
from efro.dataclassio._base import IOAttrs
|
||||
from efro.dataclassio._outputter import _Outputter
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
@ -41,6 +43,7 @@ class _Inputter(Generic[T]):
|
||||
self._coerce_to_float = coerce_to_float
|
||||
self._allow_unknown_attrs = allow_unknown_attrs
|
||||
self._discard_unknown_attrs = discard_unknown_attrs
|
||||
self._soft_default_validator: Optional[_Outputter] = None
|
||||
|
||||
if not allow_unknown_attrs and discard_unknown_attrs:
|
||||
raise ValueError('discard_unknown_attrs cannot be True'
|
||||
@ -216,18 +219,28 @@ class _Inputter(Generic[T]):
|
||||
|
||||
# Go through all fields looking for any not yet present in our data.
|
||||
# If we find any such fields with a soft-default value or factory
|
||||
# defined, inject that value into our args.
|
||||
# defined, inject that soft value into our args.
|
||||
for key, aparsed in parsed_field_annotations.items():
|
||||
if key not in args:
|
||||
ioattrs = aparsed[1]
|
||||
if (ioattrs is not None and
|
||||
(ioattrs.soft_default is not ioattrs.MISSING
|
||||
or ioattrs.soft_default_factory is not ioattrs.MISSING)):
|
||||
if ioattrs.soft_default is not ioattrs.MISSING:
|
||||
args[key] = ioattrs.soft_default
|
||||
else:
|
||||
assert callable(ioattrs.soft_default_factory)
|
||||
args[key] = ioattrs.soft_default_factory()
|
||||
if key in args:
|
||||
continue
|
||||
ioattrs = aparsed[1]
|
||||
if (ioattrs is not None and
|
||||
(ioattrs.soft_default is not ioattrs.MISSING
|
||||
or ioattrs.soft_default_factory is not ioattrs.MISSING)):
|
||||
if ioattrs.soft_default is not ioattrs.MISSING:
|
||||
soft_default = ioattrs.soft_default
|
||||
else:
|
||||
assert callable(ioattrs.soft_default_factory)
|
||||
soft_default = ioattrs.soft_default_factory()
|
||||
args[key] = soft_default
|
||||
|
||||
# Make sure these values are valid since we didn't run
|
||||
# them through our normal input type checking.
|
||||
|
||||
self._type_check_soft_default(
|
||||
value=soft_default,
|
||||
anntype=aparsed[0],
|
||||
fieldpath=(f'{fieldpath}.{key}' if fieldpath else key))
|
||||
|
||||
try:
|
||||
out = cls(**args)
|
||||
@ -238,6 +251,23 @@ class _Inputter(Generic[T]):
|
||||
setattr(out, EXTRA_ATTRS_ATTR, extra_attrs)
|
||||
return out
|
||||
|
||||
def _type_check_soft_default(self, value: Any, anntype: Any,
|
||||
fieldpath: str) -> None:
|
||||
from efro.dataclassio._outputter import _Outputter
|
||||
|
||||
# Counter-intuitively, we create an outputter as part of
|
||||
# our inputter. Soft-default values are already internal types;
|
||||
# we need to make sure they can go out from there.
|
||||
if self._soft_default_validator is None:
|
||||
self._soft_default_validator = _Outputter(
|
||||
obj=None,
|
||||
create=False,
|
||||
codec=self._codec,
|
||||
coerce_to_float=self._coerce_to_float)
|
||||
self._soft_default_validator.soft_default_check(value=value,
|
||||
anntype=anntype,
|
||||
fieldpath=fieldpath)
|
||||
|
||||
def _dict_from_input(self, cls: type, fieldpath: str, anntype: Any,
|
||||
value: Any, ioattrs: Optional[IOAttrs]) -> Any:
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
@ -39,12 +39,23 @@ class _Outputter:
|
||||
def run(self) -> Any:
|
||||
"""Do the thing."""
|
||||
|
||||
assert dataclasses.is_dataclass(self._obj)
|
||||
|
||||
# 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 soft_default_check(self, value: Any, anntype: Any,
|
||||
fieldpath: str) -> None:
|
||||
"""(internal)"""
|
||||
self._process_value(type(value),
|
||||
fieldpath=fieldpath,
|
||||
anntype=anntype,
|
||||
value=value,
|
||||
ioattrs=None)
|
||||
|
||||
def _process_dataclass(self, cls: type, obj: Any, fieldpath: str) -> Any:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
@ -16,10 +16,12 @@ import datetime
|
||||
from typing import TYPE_CHECKING, TypeVar, get_type_hints
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from efro.dataclassio._base import _parse_annotated, _get_origin, SIMPLE_TYPES
|
||||
from efro.dataclassio._base import (_parse_annotated, _get_origin,
|
||||
SIMPLE_TYPES)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
from efro.dataclassio._base import IOAttrs
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
@ -214,6 +216,7 @@ class PrepSession:
|
||||
self.prep_type(cls,
|
||||
attrname,
|
||||
anntype,
|
||||
ioattrs=ioattrs,
|
||||
recursion_level=recursion_level + 1)
|
||||
|
||||
# Success! Store our resolved stuff with the class and we're done.
|
||||
@ -228,13 +231,12 @@ class PrepSession:
|
||||
return prepdata
|
||||
|
||||
def prep_type(self, cls: type, attrname: str, anntype: Any,
|
||||
recursion_level: int) -> None:
|
||||
ioattrs: Optional[IOAttrs], recursion_level: int) -> None:
|
||||
"""Run prep on a dataclass."""
|
||||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
# If we run into classes containing themselves, we may have
|
||||
# to do something smarter to handle it.
|
||||
if recursion_level > MAX_RECURSION:
|
||||
raise RuntimeError('Max recursion exceeded.')
|
||||
|
||||
@ -257,6 +259,32 @@ class PrepSession:
|
||||
f'Unsupported type found for \'{attrname}\' on {cls}:'
|
||||
f' {anntype}')
|
||||
|
||||
# If a soft_default value/factory was passed, we do some basic
|
||||
# type checking on the top-level value here. We also run full
|
||||
# recursive validation on values later during inputting, but this
|
||||
# should catch at least some errors early on, which can be
|
||||
# useful since soft_defaults are not static type checked.
|
||||
if ioattrs is not None:
|
||||
have_soft_default = False
|
||||
soft_default: Any = None
|
||||
if ioattrs.soft_default is not ioattrs.MISSING:
|
||||
have_soft_default = True
|
||||
soft_default = ioattrs.soft_default
|
||||
elif ioattrs.soft_default_factory is not ioattrs.MISSING:
|
||||
assert callable(ioattrs.soft_default_factory)
|
||||
have_soft_default = True
|
||||
soft_default = ioattrs.soft_default_factory()
|
||||
|
||||
# Do a simple type check for the top level to catch basic
|
||||
# soft_default mismatches early; full check will happen at
|
||||
# input time.
|
||||
if have_soft_default:
|
||||
if not isinstance(soft_default, origin):
|
||||
raise TypeError(
|
||||
f'{cls} attr {attrname} has type {origin}'
|
||||
f' but soft_default value is type {type(soft_default)}'
|
||||
)
|
||||
|
||||
if origin in SIMPLE_TYPES:
|
||||
return
|
||||
|
||||
@ -273,6 +301,7 @@ class PrepSession:
|
||||
self.prep_type(cls,
|
||||
attrname,
|
||||
childtypes[0],
|
||||
ioattrs=None,
|
||||
recursion_level=recursion_level + 1)
|
||||
return
|
||||
|
||||
@ -304,6 +333,7 @@ class PrepSession:
|
||||
self.prep_type(cls,
|
||||
attrname,
|
||||
childtypes[1],
|
||||
ioattrs=None,
|
||||
recursion_level=recursion_level + 1)
|
||||
return
|
||||
|
||||
@ -325,6 +355,7 @@ class PrepSession:
|
||||
self.prep_type(cls,
|
||||
attrname,
|
||||
childtype,
|
||||
ioattrs=None,
|
||||
recursion_level=recursion_level + 1)
|
||||
return
|
||||
|
||||
@ -362,6 +393,7 @@ class PrepSession:
|
||||
self.prep_type(cls,
|
||||
attrname,
|
||||
childtype,
|
||||
None,
|
||||
recursion_level=recursion_level + 1)
|
||||
|
||||
def prep_enum(self, enumtype: type[Enum]) -> None:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user