removed this_struct and added a lambda instead

This commit is contained in:
Tim Rid 2022-02-20 17:07:41 +01:00
parent 935f021a4f
commit acc9f6a9e4
3 changed files with 40 additions and 34 deletions

View file

@ -2,8 +2,7 @@ from construct_typed.generic import constr
from construct_typed.dataclass_struct import (
DataclassBitStruct,
DataclassStruct,
csfield,
this_struct
csfield
)
from construct_typed.generic import (
Adapter,
@ -24,7 +23,6 @@ __all__ = [
"TEnumConstruct",
"TFlags",
"TFlagsConstruct",
"this_struct",
"Adapter",
"ConstantOrContextLambda",
"Construct",

View file

@ -224,23 +224,6 @@ class DataclassConstruct(Adapter[t.Any, t.Any, T, T]):
return ret_dict
# Helper object for defining the `constr` of a `struct`. Will be replaced with the proper construct, when class is created.
this_struct: Construct[t.Any, t.Any] = Construct()
def _replace_this_struct(constr: "Construct[t.Any, t.Any]", replacement: t.Any) -> None:
"""Recursive search for `this_struct` in all SubConstructs and replace it with AttrsStruct"""
subcon = getattr(constr, "subcon", None)
if subcon is this_struct:
setattr(constr, "subcon", replacement)
elif subcon is not None:
_replace_this_struct(subcon, replacement)
else:
raise ValueError(
"Could not find `this_struct`. Only SubConstructs are supported"
)
@__dataclass_transform__(field_descriptors=(csfield,), kw_only_default=True)
class DataclassStruct:
"""
@ -258,7 +241,7 @@ class DataclassStruct:
are internally used for creating a `Const` or `Default` construct but also adds the default/const value to the DataclassStruct while creating
it via __init__.
:param constr: This can be used if the structure is nested inside a Subconstruct. To represent this struct use the constant `this_struct`.
:param constr: Lambda for creating the construct object. This can be used if the DataclassStruct is nested inside a Subconstruct. Eg. `lambda cls: cs.Bitwise(cls)`.
:param reverse: Flag if the fields of the dataclass should be reversed
Example::
@ -276,13 +259,15 @@ class DataclassStruct:
@classmethod
def __init_subclass__(
cls,
constr: "cs.Construct[t.Any, t.Any]" = this_struct,
cls: t.Type[T],
constr: t.Callable[
[DataclassConstruct[T]], Construct[t.Any, t.Any]
] = lambda cls: cls,
reverse_fields: bool = False,
) -> None:
# validate types
if not isinstance(constr, cs.Construct): # type: ignore
raise ValueError("`constr` parameter has to be an `Construct` object")
if not callable(constr):
raise ValueError("`constr` parameter has to be a function or lambda")
if not isinstance(reverse_fields, bool): # type: ignore
raise ValueError("`reverse_fields` parameter has to be an `bool` object")
@ -290,14 +275,12 @@ class DataclassStruct:
dataclasses.dataclass(cls, kw_only=True) # type: ignore
# create construct format
dc_constr = DataclassConstruct(cls, reverse_fields)
if constr is this_struct:
constr = dc_constr
else:
_replace_this_struct(constr, dc_constr)
dc_constr = constr(DataclassConstruct(cls, reverse_fields))
if not isinstance(dc_constr, cs.Construct): # type: ignore
raise ValueError("`constr` sould return a `Construct` object")
# save construct format and make the class compatible to `Constructable` protocol
setattr(cls, "__constr__", lambda: constr)
setattr(cls, "__constr__", lambda: dc_constr)
# the `construct` library is using the [] access internally, so struct objects
# should also make this possible and not only via the dot access.
@ -379,8 +362,10 @@ class DataclassBitStruct(DataclassStruct):
@classmethod
def __init_subclass__(
cls,
constr: "cs.Construct[t.Any, t.Any]" = this_struct,
cls: t.Type[T],
constr: t.Callable[
[DataclassConstruct[T]], Construct[t.Any, t.Any]
] = lambda cls: cls,
reverse_fields: bool = False,
) -> None:
cls = DataclassStruct.__init_subclass__.__func__(cls, cs.Bitwise(constr), reverse_fields) # type: ignore
DataclassStruct.__init_subclass__.__func__(cls, lambda cls: cs.Bitwise(constr(cls)), reverse_fields) # type: ignore

View file

@ -301,6 +301,29 @@ def test_dataclass_struct_doc() -> None:
)
def test_dataclass_bitwise() -> None:
class TestContainer(DataclassStruct, constr=lambda cls: cs.Bitwise(cls)):
a: int = csfield(cs.BitsInteger(7))
b: int = csfield(cs.Bit)
c: int = csfield(cs.BitsInteger(8))
common(
constr(TestContainer),
b"\xFD\x12",
TestContainer(a=0x7E, b=1, c=0x12),
2,
)
# check __getattr__
c = TestContainer.__constr__()
assert c.subcon.a.name == "a"
assert c.subcon.b.name == "b"
assert c.subcon.c.name == "c"
assert isinstance(c.subcon.a.subcon, cs.BitsInteger)
assert c.subcon.b.subcon is cs.Bit
assert isinstance(c.subcon.c.subcon, cs.BitsInteger)
def test_dataclass_bitstruct() -> None:
class TestContainer(DataclassBitStruct):
a: int = csfield(cs.BitsInteger(7))