Skip to main content

Python package to translate C struct to classes

Project description

cstructimpl

A Python package for translating C structs into Python classes.

PyPI version License Python Versions


Quick Start

Install from PyPI:

pip install cstructimpl

Define your struct and parse raw bytes:

from cstructimpl import *


class Info(CStruct):
    age: Annotated[int, CInt.U8]
    height: Annotated[int, CInt.U8]


class Person(CStruct):
    info: Info
    name: Annotated[str, CStr(6)]


person = Person.c_decode(bytes([18, 170]) + b"Pippo\x00")
print(person)  # Person(info=Info(age=18, height=170), name='Pippo')

Introduction

cstructimpl makes working with binary data in Python simple and intuitive.
By subclassing CStruct, you can define Python classes that map directly to C-style structs and parse raw bytes into fully typed objects.

No manual parsing, no boilerplate — just define your struct and let the library do the heavy lifting.


Type System

At the core of the library is the BaseType protocol, which defines how types behave in the C world:

class BaseType(Protocol[T]):

    def c_size(self) -> int: ...
    def c_align(self) -> int: ...

    def c_decode(
        self,
        raw: bytes,
        *,
        is_little_endian: bool = True,
        signed: bool | None = None,
    ) -> T | None: ...

    def c_encode(
        self,
        data: T,
        *,
        is_little_endian: bool = True,
        signed: bool | None = None,
    ) -> bytes: ...

Any class that follows this protocol can act as a BaseType, controlling its own parsing, size, and alignment.

When parsing a struct:

  • If a field type is itself a BaseType, parsing happens automatically.
  • Otherwise, annotate the field with Annotated[..., BaseType] to tell the parser how to interpret it.
  • Types such as int have a default converter for a BaseType if no annotation is provided. If you want to change this behavior you need to ovveride them in the following dictionary cstructimpl.c_lib.DEFAULT_TYPE_TO_BASETYPE.

The library comes with a set of ready-to-use type definitions that cover the majority of C primitive types.

Class / Type Description
BaseType[T] Protocol that defines the interface for any encodable/decodable C-compatible type.
HasBaseType Protocol for classes that return their own associated BaseType.
GetType Wrapper calling c_get_type() on classes implementing HasBaseType. Enables automatic size, alignment, decode, encode access.
CInt Enum covering signed/unsigned C integer types (I8/U8 → I128/U128).
CBool Boolean BaseType with a C-compatible single-byte representation.
CFloat Enum of IEEE‑754‑compliant floating‑point formats (F32, F64).
CArray[T] Generic BaseType for fixed‑length arrays of a given element type.
CPadding Represents unused/padding bytes between struct fields.
CStr C‑style null‑terminated string of fixed max length.
CMapper[T,U] Adapts between a BaseType[U] and custom Python type T. Useful for enums or custom conversions.

Examples

Here are a few practical examples showing how cstructimpl works in real-world scenarios.

Basic Deserialization

Define a simple struct with two fields:

class Point(CStruct):
    x: Annotated[int, CInt.U8]
    y: Annotated[int, CInt.U8]


assert Point.c_size() == 2
assert Point.c_align() == 1
assert Point.c_decode(bytes([1, 2])) == Point(1, 2)

Serializing a Class

Create a class instance and serlialize it to raw bytes

class Rect(CStruct):
    width: Annotated[int, CInt.U8]
    height: Annotated[int, CInt.U8] = 10

rect = Rect(2)
assert rect.c_encode() == bytes([2, 10])

Nested Structs

You can embed structs inside other structs:

class Dimensions(CStruct):
    width: Annotated[int, CInt.U8]
    height: Annotated[int, CInt.U8]


class Rectangle(CStruct):
    id: Annotated[int, CInt.U16]
    dims: Dimensions


assert Rectangle.c_size() == 4
assert Rectangle.c_align() == 2
assert Rectangle.c_decode(bytes([1, 0, 2, 3])) == Rectangle(1, Dimensions(2, 3))

Strings in Structs

Support for C-style null-terminated strings:

class Message(CStruct):
    length: Annotated[int, CInt.U16]
    text: Annotated[str, CStr(5)]


raw = bytes([5, 0]) + b"Helo\x00"
assert Message.c_decode(raw) == Message(5, "Helo")

Enums with Autocast

Automatically cast numeric values into Python Enums:

class Mood(Enum):
    HAPPY = 0
    SAD = 1


class Person(CStruct):
    age: Annotated[int, CInt.U16]
    mood: Annotated[Mood, CInt.U8, Autocast()]


raw = bytes([18, 0, 1, 0])
assert Person.c_decode(raw) == Person(18, Mood.SAD)

Arrays of Structs

Define fixed-size arrays of structs inside another struct:

class Item(CStruct, align=2):
    a: Annotated[int, CInt.U8]
    b: Annotated[int, CInt.U8]
    c: Annotated[int, CInt.U8]


class ItemList(CStruct):
    items: Annotated[list[Item], CArray(Item, 3)]


data = bytes(range(1, 13))  # 3 items x 4 bytes each
parsed = ItemList.c_decode(data)

assert parsed == ItemList([
    Item(1, 2, 3),
    Item(5, 6, 7),
    Item(9, 10, 11),
])

Custom BaseType

Hey! Is there a type that serializes an hash-map of list of structs of ...?

Yeah, sure there is! You can do it yourself!

cstructimpl lets you define your own BaseType implementations to handle any kind of data that is not present among the built-in primitives.

For example, here's a custom type that interprets a raw integer as a Unix timestamp, returning a Python datetime object:

class UnixTimestamp(BaseType[datetime]):
    def c_size(self) -> int:
        return 4

    def c_align(self) -> int:
        return 4

    def c_decode(self, raw: bytes, *, byteorder="little", signed=False) -> datetime:
        ts = int.from_bytes(raw, byteorder=byteorder, signed=signed)
        return datetime.utcfromtimestamp(ts)

    def c_encode(self): pass


    @dataclass
    class LogEntry(CStruct):
        timestamp: Annotated[datetime, UnixTimestamp()]
        level: Annotated[int, CInt.U8]


    parsed = LogEntry.c_decode(bytes([255, 0, 0, 0, 3, 0, 0, 0]))
    assert parsed == LogEntry(datetime.fromtimestamp(255), 3)

Autocast

Sometimes raw numeric values carry semantic meaning. In C, this is usually handled with enums.
With cstructimpl, you can automatically reinterpret values into enums (or other types) using Autocast.

from cstructimpl import *


class ResultType(Enum):
    OK = 0
    ERROR = 1


class Person(CStruct):
    kind: Annotated[ResultType, CInt.U8, Autocast()]
    error_code: Annotated[int, CInt.I32]

This is equivalent to writing a custom builder:

from cstructimpl import *


class ResultType(Enum):
    OK = 0
    ERROR = 1


class Person(CStruct):
    kind: Annotated[ResultType, CMapper(CInt.U8, lambda u8: ResultType(u8))]
    error_code: Annotated[int, CInt.I32]

But much simpler and less error-prone.


Features

  • Define Python classes that map directly to C structs
  • Parse raw bytes into typed objects with a single method call
  • Serialize a class to raw bytes using built-in type system
  • Built-in type system for common C primitives
  • Support for nested structs
  • Flexible extension via the BaseType protocol

Use Cases

  • Parsing binary network protocols
  • Working with binary file formats
  • Interfacing with C libraries and data structures
  • Replacing boilerplate parsing code with clean, type-safe classes

Documentation

More detailed usage examples and advanced topics are available in the documentation.


Contributing

Contributions are welcome!

If you'd like to improve cstructimpl, please open an issue or submit a pull request on GitHub.


License

This project is licensed under the terms of the Apache-2.0 License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cstructimpl-0.5.0.tar.gz (19.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cstructimpl-0.5.0-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file cstructimpl-0.5.0.tar.gz.

File metadata

  • Download URL: cstructimpl-0.5.0.tar.gz
  • Upload date:
  • Size: 19.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for cstructimpl-0.5.0.tar.gz
Algorithm Hash digest
SHA256 da8a49d1a5d05be81f6e434180297e480b1381a3a7185aa580b84d952d3bdf81
MD5 1e7fca356ab739f66981625d3cbc47f5
BLAKE2b-256 883428f142049c1480cd9aab9672818808703db26a9fb2421faa5fee95ea4469

See more details on using hashes here.

File details

Details for the file cstructimpl-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: cstructimpl-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 19.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for cstructimpl-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dad0c170ce9cb9f0d2641b8ce23e9d06b91f289b12ce4ec917e07a4a41b5afe0
MD5 bc6afd409014c6ca8a36138b991d4c1f
BLAKE2b-256 4c34f35390f7c5fe88c4136551719f8f19c928f9e94e648b94d2c82257f4b6ae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page