Skip to main content

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.1.2

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.1.2
File Size Uploaded
bytestruct-0.1.2.tar.gz 67.4 kB Details

Built distribution (wheel)

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

Total release size: 86.4 kB

Release files / bytestruct-0.1.2.tar.gz

Download URL bytestruct-0.1.2.tar.gz
Size 67.4 kB
Tags Source
SHA-256 checksum
How to use checksums
2042b974e3b8e5a7019bc522abecdb7162f0fce6a707545662f27c3a476274b2
BLAKE2b-256 checksum
How to use checksums
a234f4fd0b95a29217247899c03fe0dad258bc0a34ec1770bd1ab435cd00e91f
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 17, 2026.

Transparency log

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

Download URL bytestruct-0.1.2-py3-none-any.whl
Size 19.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
23bc9050a08b28678dd4a00455c5fedb9e5585743209fe78afcfc2ad9ca7e067
BLAKE2b-256 checksum
How to use checksums
8f224c21643df3400a1536029dd1feb53d7c0e39a8483c09fa0841b2791953fc
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 17, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.2 This release

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