Skip to content

Commit

Permalink
Add support to put calculated field allways last.
Browse files Browse the repository at this point in the history
  • Loading branch information
HeMan committed Nov 8, 2020
1 parent cf6dc8b commit 3c57ba6
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 8 deletions.
30 changes: 22 additions & 8 deletions binmap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,14 @@ def enumfield(
return dataclasses.field(default=default, metadata={"enum": enumclass})


def calculatedfield(function: Callable, last=False) -> dataclasses.Field:
"""
Field generator function for calculated fields
:param Callable function: function that calculates the field.
:return: dataclass field
"""
def calculatedfield(function: Callable) -> dataclasses.Field:
return dataclasses.field(default=0, metadata={"function": function})
return dataclasses.field(default=0, metadata={"function": function, "last": last})


@dataclasses.dataclass
Expand Down Expand Up @@ -213,6 +213,7 @@ def __init_subclass__(cls, byteorder: str = ">"):

cls.__formatstring = byteorder

lastfield = ""
for field_ in dataclasses.fields(cls):
if field_.name.startswith("_BinmapDataclass__"):
continue
Expand All @@ -230,7 +231,13 @@ def __init_subclass__(cls, byteorder: str = ">"):
_type = field_.default * _type
if type_hints[field_.name] in (types.string, types.pascalstring, str):
_type = str(field_.metadata["length"]) + _type
cls.__formatstring += _type
if "last" in field_.metadata and field_.metadata["last"]:
if lastfield != "":
raise ValueError("Can't have more than one last")
lastfield = _type
else:
cls.__formatstring += _type
cls.__formatstring += lastfield

def __bytes__(self):
"""
Expand All @@ -239,12 +246,19 @@ def __bytes__(self):
:rtype: bytes
"""
values = []
lastvalue = None
for k, v in self.__dict__.items():
if not k.startswith("_BinmapDataclass__"):
if callable(v):
values.append(v(self))
else:
values.append(v)
if k.startswith("_BinmapDataclass__"):
continue
if callable(v):
v = v(self)
if "last" in self.__datafieldsmap[k].metadata and self.__datafieldsmap[k].metadata["last"]:
lastvalue = v
continue
values.append(v)
if lastvalue is not None:
values.append(lastvalue)

return struct.pack(
self.__formatstring,
*values,
Expand Down
63 changes: 63 additions & 0 deletions tests/test_binmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,21 @@ def chk(self) -> types.unsignedchar:
checksum: types.unsignedchar = binmap.calculatedfield(chk)


class CalculatedFieldLast(binmap.BinmapDataclass):
temp: types.signedchar = 0

def chk_last(self):
checksum = 0
for k, v in self.__dict__.items():
if k.startswith("_") or callable(v):
continue
checksum += v
return checksum & 0xFF

checksum: types.unsignedchar = binmap.calculatedfield(chk_last, last=True)
hum: types.unsignedchar = 0


class TestCalculatedField:
def test_calculated_field(self):
cf = CalculatedField()
Expand All @@ -620,3 +635,51 @@ def test_calculated_field_binary(self):
with pytest.raises(ValueError) as excinfo:
CalculatedField(b"\xe4\x18\x00")
assert "Wrong calculated value" in str(excinfo)

def test_calculated_field_last(self):

cfl = CalculatedFieldLast()
cfl.temp = 10
cfl.hum = 20

assert cfl.checksum == 30
assert bytes(cfl) == b"\x0a\x14\x1e"

def test_calculated_field_last_inherit(self):
class CalculatedFieldLastInherit(CalculatedFieldLast):
lux: types.unsignedinteger = 0

cfli = CalculatedFieldLastInherit()
cfli.temp = 10
cfli.hum = 20
cfli.lux = 401
assert bytes(cfli) == b"\x0a\x14\x00\x00\x01\x91\xaf"

with pytest.raises(ValueError) as excinfo:
CalculatedFieldLastInherit(b"\x0b\x20\x00\x00\x01\x30\x00")
assert "Wrong calculated value" in str(excinfo)

def test_calculated_field_multi_last(self):
with pytest.raises(ValueError) as excinfo:

class CalculatedFieldMultiLast(binmap.BinmapDataclass):
temp: types.unsignedchar = 0

def chk(self):
return 0

checksum1: types.unsignedchar = binmap.calculatedfield(chk, last=True)
checksum2: types.unsignedchar = binmap.calculatedfield(chk, last=True)

assert "Can't have more than one last" in str(excinfo)

def test_calculated_field_multi_last_inherit(self):
with pytest.raises(ValueError) as excinfo:

class CalculatedFieldMultiLastInherit(CalculatedFieldLast):
def chk2(self):
return 0

checksum2: types.unsignedchar = binmap.calculatedfield(chk2, last=True)

assert "Can't have more than one last" in str(excinfo)

0 comments on commit 3c57ba6

Please sign in to comment.