Getting started¶
A first record¶
A record is a dataclass that derives from BinaryStruct. Each field's annotation names its wire type,
and fields appear on the wire in declaration order:
from dataclasses import dataclass
from bytestruct import BinaryStruct, u8, u16, u32
@dataclass
class Point(BinaryStruct, byte_order="<"):
flags: u8
x: u16
y: u32
assert Point.SIZE == 7 # 1 + 2 + 4, no alignment padding
u8, u16 and u32 are Annotated[int, ...] aliases: to a type checker, Point.x is an int. The
byte_order class argument ("<" little-endian, the default, or ">" big-endian) applies to every field
that does not pin its own.
Build and parse¶
build() encodes an instance; parse() decodes one from the start of a buffer:
point = Point(flags=1, x=2, y=3)
data = point.build()
assert data == b"\x01" + b"\x02\x00" + b"\x03\x00\x00\x00"
assert Point.parse(data) == point
assert Point.parse(data + b"more bytes") == point # trailing bytes are ignored
assert Point.parse(bytearray(data)) == point # bytes, bytearray and memoryview are all accepted
To parse several records out of one buffer, read them from a ByteReader, which keeps track of the
position:
from bytestruct import ByteReader
reader = ByteReader(Point(1, 2, 3).build() + Point(4, 5, 6).build())
assert Point.read(reader) == Point(1, 2, 3)
assert Point.read(reader) == Point(4, 5, 6)
assert reader.remaining == 0
Errors¶
Every failure is a BinaryDataError, which is a ValueError. Parsing raises a BinaryParseError (a
TruncatedDataError when the data is too short) and building raises a BinaryBuildError. The message
says which record, which field and which absolute offset:
from bytestruct import BinaryBuildError, BinaryDataError, TruncatedDataError
try:
Point.parse(b"\x01\x02\x00\x03")
except TruncatedDataError as e:
assert str(e) == "Point.y at offset 3: expected 4 bytes, found 1"
assert (e.record, e.field, e.offset) == ("Point", "y", 3)
try:
Point(1, 2, 2**32).build()
except BinaryBuildError as e:
assert e.record == "Point"
assert issubclass(BinaryDataError, ValueError)
Next steps¶
- Records covers every field type, constants, padding and variable-length fields.
- Codecs covers the combinators for variable-length data.
- ByteReader covers parsing formats by hand.
- Errors covers the exception tree and error locations.
- Extending bytestruct shows how to write your own field types and codecs.
- The Cookbook has complete formats, end to end.
- The Type reference has a round-trip example for every public type.