Skip to main content

construct-typing

PyPI PyPI - Implementation PyPI - Python Version GitHub

This project is an extension of the python package construct, which is a powerful declarative and symmetrical parser and builder for binary data. This Repository consists of two packages:

  • construct-stubs: Adding .pyi for the whole construct 2.10 package (according to PEP 561 stub-only packages)
  • construct_typed: Adding additional classes that help with autocompletion and additional type hints.

Installation

This package complies with PEP 561. So most of the static code analysers will recognise the stubs automatically. The installation only requires:

pip install construct-typing

Tests

The stubs are tested against the pytests of the construct package in a slightly modified form. Since the tests are relatively detailed I think most cases are covered.

The new typed constructs have new written pytests, which also passes all pytests and the static type checkers.

The following static type checkers are fully supported:

  • mypy
  • pyright
  • ty (experimental, since ty itself is still in development)

Development

This project uses uv as a project management tool. To set up your development environment, run the following command:

uv sync

To run the unit tests, run:

uv run poe test

To run the linter/code formatter (including auto fix), run:

uv run poe lint

To run all supported type checkers, run:

uv run poe typecheck

To run unit tests, linter/code formatter and type checkers, run:

uv run poe check-all

Explanation

Stubs

The construct-stubs package is used for creating type hints for the orignial construct package. In particular the build and parse methods get type hints. So the core of the stubs are the TypeVar's ParsedType and BuildTypes:

  • Construct.build: converts an object of one of the types defined by BuildTypes to a bytes object.
  • Construct.parse: converts a bytes object to an object of type ParsedType.

For each Construct the stub file defines to which type it parses to and from which it can be build. For example:

Construct parses to (ParsedType) builds from (BuildTypes)
Int16ub int int
Bytes bytes bytes, bytearray or memoryview
Array(5, Int16ub) ListContainer[int] typing.List[int]
Struct("i" / Byte) Container[typing.Any] typing.Dict[str, typing.Any] or None

The problem is to describe the more complex constructs like:

  • Sequence, FocusedSeq which has heterogenous subcons in comparison to an Array with only homogenous subcons.
  • Struct, BitStruct, LazyStruct, Union which has heterogenous and named subcons.

Currently only the very unspecific type typing.Any can be used as type hint (maybe in the future it can be optimised a little, when variadic generics become available). But the biggest disadvantage is that autocompletion for the named subcons is not available.

Note: The stubs are based on construct in Version 2.10.

Typed

!!! EXPERIMENTAL VERSION !!!

To include autocompletion and further enhance the type hints for these complex constructs the construct_typed package is used as an extension to the original construct package. It is mainly a few Adapters with the focus on type hints.

It implements the following new constructs:

  • DataclassStruct: similar to construct.Struct but strictly tied to DataclassMixin and @dataclasses.dataclass
  • DataclassBitStruct: similar to construct.BitStruct but strictly tied to DataclassMixin and @dataclasses.dataclass
  • TEnum: similar to construct.Enum but strictly tied to a TEnumBase class
  • TFlagsEnum: similar to construct.FlagsEnum but strictly tied to a TFlagsEnumBase class

These types are strongly typed, which means that there is no difference between the ParsedType and the BuildTypes. So to build one of the constructs the correct type is enforced. The disadvantage is that the code will be a little bit longer, because you can not for example use a normal dict to build an DataclassStruct. But the big advantage is, that if you use the correct container type instead of a dict, the static code analyses can do its magic and find potential type errors and missing values without running the code itself.

A short example:

import dataclasses
import typing as t
from construct import Array, Byte, Bytes, Int8ub, this
from construct_typed import DataclassMixin, DataclassStruct, EnumBase, TEnum, csfield, csfield_const

class Orientation(EnumBase):
    HORIZONTAL = 0
    VERTICAL = 1

@dataclasses.dataclass
class Image(DataclassMixin):
    signature: bytes = csfield_const(Bytes(3), b"BMP")
    orientation: Orientation = csfield(TEnum(Int8ub, Orientation))
    width: int = csfield(Int8ub)
    height: int = csfield(Int8ub)
    pixels: t.List[int] = csfield(Array(this.width * this.height, Byte))

format = DataclassStruct(Image)
obj = Image(
    orientation=Orientation.VERTICAL,
    width=3,
    height=2,
    pixels=[7, 8, 9, 11, 12, 13],
)
print(format.build(obj))
print(format.parse(b"BMP\x01\x03\x02\x07\x08\t\x0b\x0c\r"))

Output:

b'BMP\x01\x03\x02\x07\x08\t\x0b\x0c\r'
Image: 
    signature = b'BMP' (total 3)
    orientation = 1
    width = 3
    height = 2
    pixels = ListContainer: 
        7
        8
        9
        11
        12
        13

Using constants in DataclassStruct

If you want a simple, fixed constant in a DataclassStruct, use csfield_const. It automatically wraps the given value in a cs.Const construct and sets the value directly, without a constructor parameter, when the DataclassStruct instance is created.

import dataclasses
from construct import Int8ub, Bytes
from construct_typed import DataclassMixin, csfield, csfield_const
import inspect


@dataclasses.dataclass
class Image(DataclassMixin):
    signature: bytes = csfield_const(Bytes(3), b"BMP")  # <-- no constructor parameter is generated
    width: int = csfield(Int8ub)
    height: int = csfield(Int8ub)


print(inspect.signature(Image))  # -> (width: int, height: int) -> None

Using defaults in DataclassStruct

If you want a simple, fixed default value for a parameter in a DataclassStruct, use csfield_default. It automatically wraps the given value in a cs.Default construct and sets it as the field's default value. This default value can still be overridden through the DataclassStruct constructor. As a consequence, all fields that follow it in the constructor must be marked as kw_only.

import dataclasses
from construct import Int8ub, Bytes
from construct_typed import DataclassMixin, csfield, csfield_default
import inspect


@dataclasses.dataclass
class Image(DataclassMixin):
    some_value: int = csfield(Int8ub)
    signature: bytes = csfield_default(Bytes(3), default=b"BMP")  # <-- constructor parameter is generated with default value b"BMP"
    width: int = csfield(Int8ub, kw_only=True)  # <-- kw_only is required for all fields after a default field
    height: int = csfield(Int8ub, kw_only=True)  # <-- kw_only is required for all fields after a default field


print(inspect.signature(Image))  # -> (some_value: int, signature: bytes = b'BMP', *, width: int, height: int) -> None

For more complex cases, where the default value needs to be computed dynamically from the context, use csfield_noinit(Default(...)) instead, since the context is not yet known when the DataclassStruct instance is created. For example:

import dataclasses
from construct import Int8ub, Int16ub, Default, this
from construct_typed import DataclassMixin, csfield, csfield_noinit
import inspect


@dataclasses.dataclass
class Image(DataclassMixin):
    width: int = csfield(Int8ub)
    height: int = csfield(Int8ub)
    min_buffer_size: int | None = csfield_noinit(  # <-- "| None" is required
        Default(Int16ub, this.width * this.height)
    )

print(inspect.signature(Image))  # -> (width: int, height: int) -> None

Using generic constructs that build from None

Some constructs, such as cs.Computed, cs.Rebuild, cs.Padding, cs.Tell, cs.Pass and cs.Terminated, are only computed dynamically at parse/build time. Fields for these constructs must be declared with csfield_noinit, because their values are not yet known when a DataclassStruct instance is created through the constructor, and are therefore automatically initialized to None. As a result, these fields cannot be overridden through the constructor. Because the constructor always sets them to None, their type must always be <type> | None.

import dataclasses
from construct import Int8ub, Computed
from construct.core import Tell
from construct_typed import DataclassMixin, csfield, csfield_noinit
import inspect


@dataclasses.dataclass
class Image(DataclassMixin):
    width: int = csfield(Int8ub)
    height: int = csfield(Int8ub)
    pos: int | None = csfield_noinit(Tell)  # <-- "| None" is required
    size: int | None = csfield_noinit(  # <-- "| None" is required
        Computed(lambda ctx: ctx.width * ctx.height)
    )

print(inspect.signature(Image))  # -> (width: int, height: int) -> None

Note: csfield_noinit can technically also be used with cs.Const or a non-lambda cs.Default, but the field then stays None until the struct is parsed, since the value is not assigned by the constructor. If the value should already be available right after construction, use csfield_const() or csfield_default() instead.

Release files for construct-typing 0.8.1

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

Source distribution (sdist)

Source distribution for construct-typing 0.8.1
File Size Uploaded
construct_typing-0.8.1.tar.gz 53.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for construct-typing 0.8.1
File Interpreter ABI Platform
construct_typing-0.8.1-py3-none-any.whl Python 3 none any Details

Total release size: 79.8 kB

Release files / construct_typing-0.8.1.tar.gz

Download URL construct_typing-0.8.1.tar.gz
Size 53.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b3c33246b83f187f21fbfa9e229cf7225fe42823f3d598328b973c844071c456
BLAKE2b-256 checksum
How to use checksums
61dede378332a7cb2f62df134e0b5915a0fea939219c80ecee99b29da8ba49c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 23, 2026.

Transparency log

Release files / construct_typing-0.8.1-py3-none-any.whl

Download URL construct_typing-0.8.1-py3-none-any.whl
Size 26.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9b5845dbaf959c9d960f5105f9ab3533227c4da2b76baa57d1f1c0aa2a088cdb
BLAKE2b-256 checksum
How to use checksums
95f47dd59e010765f5c0043a5cf59e0f3e6748cd966491645bbb10b88ad24c82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.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 Jul 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.1 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

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