Skip to main content

bytestruct logo

bytestruct

Python application Pypi version Docs

bytestruct parses and builds binary wire formats with the standard library alone. Fixed-size records are dataclasses whose field annotations give each field's wire type; variable-length data is described by small codecs that compose like construct's. Everything is fully typed.

The layout of a record is compiled into struct.Struct objects once, when the class is defined, so parsing a record is a single unpack_from per byte-order run.

Documentation: https://doronz88.github.io/bytestruct/, including a migration guide from construct.

Install

python3 -m pip install -U bytestruct

Fixed-size records

from dataclasses import dataclass, field
from enum import IntEnum
from typing import Annotated

from bytestruct import BinaryStruct, Const, PaddedString, u16, u32


class Kind(IntEnum):
    FILE = 1
    DIRECTORY = 2


@dataclass
class Header(BinaryStruct, byte_order="<"):
    magic: Annotated[u32, Const(0x1F3D5B79)] = field(default=0x1F3D5B79, init=False)
    size: u32
    name: Annotated[str, PaddedString(16)]
    kind: Annotated[Kind, u16]  # parsed into the enum type


data = Header(size=1024, name="notes.txt", kind=Kind.FILE).build()  # a bad value -> BinaryBuildError
header = Header.parse(data)  # short input -> TruncatedDataError, wrong magic -> UnexpectedConstantError
assert header.kind is Kind.FILE

Wire fields are positional __init__ parameters in wire order. A field declared = field(default=..., init=False) is left out of __init__: parse skips its bytes (padding) and build packs its default (on a Const field, the constant). Fields without a wire type may follow the wire fields and are ignored by parse/build. Records cannot be subclassed.

Available field types include u8..u64, i16..i64, byte-order pinned u32le/u32be, Bytes(n), PaddedString(n), uuid_t, ipv4/ipv6, Array(element, n), int-enum fields and Const magics; custom ones subclass Format.

Codecs

from bytestruct import CSTRING, U32_LE, U64_LE, Aligned, Prefixed, Switch

STRING = Aligned(4, Prefixed(U32_LE, CSTRING))  # u32 length, NUL-terminated UTF-8, padded to 4 bytes
OBJECT = Switch(U32_LE, {1: STRING, 2: U64_LE})  # parses to (tag, value)

assert OBJECT.parse(OBJECT.build((1, "hi"))) == (1, "hi")

A codec is anything with read(reader) and build(value): the combinators (Prefixed, Aligned, PrefixedArray, Tuple, Switch), the leaves (CSTRING, GREEDY_STRING, GREEDY_BYTES, PASS), the scalars (U8, U16_LE/U16_BE, ..., F64_BE), Bytes(n), and record classes themselves. A record field annotated with a codec (name: Annotated[str, CSTRING]) makes the record variable-length; such fields follow the fixed-size ones. Formats with no declarative shape are read by hand with ByteReader, whose sub-readers are windows on the same buffer (nothing is copied).

Errors

Every failure raises a BinaryDataError (a ValueError): BinaryParseError (with TruncatedDataError and UnexpectedConstantError) or BinaryBuildError. Errors carry the record, field, absolute offset and the path through nested codecs, and prefix their message with them:

Header.magic at offset 0: expected 0x1f3d5b79, got 0x0
DICTIONARY -> [2] -> STRING at offset 104: expected 200 bytes, found 16

Performance

Each record class gets its own parse/read/build, compiled when the class is defined, and the work happens mostly inside struct's C code. In the benchmark (Apple M5 Pro, CPython 3.12, construct 2.10.70):

Case bytestruct construct construct, compiled ctypes
header parse 0.14 µs 2.20 µs (15.7x) 0.87 µs (6.2x) 0.09 µs (0.6x)
header build 0.10 µs 2.05 µs (20.5x) 1.17 µs (11.7x) 0.03 µs (0.3x)
100 strings parse 26.8 µs 131.8 µs (4.9x) 83.7 µs (3.1x)
100 strings build 11.3 µs 46.6 µs (4.1x) 41.6 µs (3.7x)

A record parse costs the same as a hand-written struct.unpack_from into a plain dataclass. ctypes is faster for a fixed C layout, but its fields convert from C on every read, overflow silently, and cannot be variable-length.

Contributing

See CONTRIBUTING.md for the development setup, the checks CI runs and how releases are cut.

License

MIT

Metadata

Release files for bytestruct 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bytestruct 0.2.0
File Size Uploaded
bytestruct-0.2.0.tar.gz 436.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bytestruct 0.2.0
File Interpreter ABI Platform
bytestruct-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 457.4 kB

Release files / bytestruct-0.2.0.tar.gz

Download URL bytestruct-0.2.0.tar.gz
Size 436.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ef0767fe48c0be9b2e4585565d8b28959fa04c93256ddeb7efbbd1b8f5998011
BLAKE2b-256 checksum
How to use checksums
1be437acaf4c2217e117c42163159204d749a318cb11dd58a5c39e37c482e720
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / bytestruct-0.2.0-py3-none-any.whl

Download URL bytestruct-0.2.0-py3-none-any.whl
Size 21.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ecec23ffd5f51b2f188276be83a8cb2a17910b746a0957a3306781e042e3c1cd
BLAKE2b-256 checksum
How to use checksums
c756a571db06803a5a888568dde6d672ca21cef2b2bed7271d6c6c6eb976f730
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page