[plum] Tutorial: Customizing Structure and BitFields Methods¶
This tutorial demonstrates how to generate method implementations
for subclasses of Structure
and class:BitFields
to override their behavior.
The tutorial examples use the following setup:
>>> from plum.bigendian import uint8, uint16
>>> from plum.bitfields import BitFields, bitfield
>>> from plum.structure import Structure, member
Structure Methods¶
To generate a method implementation, write a traditional Structure
subclass but assign a comma separated string to the Structure.implementation
property naming each method you want a generated implementation captured for
(omitting leading and trailing underscores). For example:
class MyStruct(Structure): m1: int = member(fmt=uint16) m2: int = member(fmt=uint8) Structure.implementation = "init,pack,unpack"
When first imported, the source file is modified and the generated code appears where the assignment was:
class MyStruct(Structure): m1: int = member(fmt=uint16) m2: int = member(fmt=uint8) def __init__(self, *, m1: int, m2: int) -> None: self[:] = (m1, m2) @classmethod def __pack__(cls, value, pieces: List[bytes], dump: Optional[Record] = None) -> None: if isinstance(value, dict): value = cls._make_structure_from_dict(value) (m_m1, m_m2) = value if dump is None: uint16.__pack__(m_m1, pieces, dump) uint8.__pack__(m_m2, pieces, dump) else: m1_dump = dump.add_record(access="m1", fmt=uint16) uint16.__pack__(m_m1, pieces, m1_dump) m2_dump = dump.add_record(access="m2", fmt=uint8) uint8.__pack__(m_m2, pieces, m2_dump) @classmethod def __unpack__(cls, buffer: bytes, offset: int, dump: Optional[Record] = None) -> Tuple["MyStruct", int]: structure = list.__new__(cls) if dump is None: m_m1, offset = uint16.__unpack__(buffer, offset, dump) m_m2, offset = uint8.__unpack__(buffer, offset, dump) else: m1_dump = dump.add_record(access="m1", fmt=uint16) m_m1, offset = uint16.__unpack__(buffer, offset, m1_dump) m2_dump = dump.add_record(access="m2", fmt=uint8) m_m2, offset = uint8.__unpack__(buffer, offset, m2_dump) structure[:] = (m_m1, m_m2) return structure, offset
It’s beneficial to capture __init__
for the benefit of IDE type ahead
or facilitate static code checkers. Modifying __pack__
and __unpack__
methods allows custom behaviors for member interactions that can’t be
accomplished by stock features in plum
.
To see all of the available generated implementations, assign “all”. For example:
class MyStruct(Structure): m1: int = member(fmt=uint16) m2: int = member(fmt=uint8) Structure.implementation = "all"
BitFields Methods¶
To generate a method implementation, write a traditional BitFields
subclass and assign a comma separated string to the BitFields.implementation
property naming each method you want a generated implementation captured for
(omitting leading and trailing underscores). For example:
class MyBitFields(BitFields): m1: int = bitfield(lsb=0, size=4) m2: int = bitfield(lsb=4, size=4) BitFields.implementation = "init,repr"
When first imported, the source file is modified and the generated code appears where the assignment was:
class MyBitFields(BitFields): m1: int = bitfield(lsb=0, size=4) m2: int = bitfield(lsb=4, size=4) def __init__(self, *, m1: int, m2: int) -> None: self.__value__ = 0 self.m1 = m1 self.m2 = m2 def __repr__(self) -> str: try: return f"{type(self).__name__}(m1={self.m1!r}, m2={self.m2!r})" except Exception: return f"{type(self).__name__}()"