Skip to content

dataclasses-struct

PyPI version Python versions Tests status Code coverage License: MIT

A simple Python package that combines dataclasses with struct for packing and unpacking Python dataclasses to fixed-length bytes representations.

Installation

This package is available on pypi:

pip install dataclasses-struct

To work correctly with mypy, an extension is required; add to your mypy.ini:

[mypy]
plugins = dataclasses_struct.ext.mypy_plugin

(See the guide for more details on type checking.)

Quick start

By default, dataclass-structs use native sizes, alignment, and byte ordering (endianness).

import dataclasses
from typing import Annotated

import dataclasses_struct as dcs  # (1)!

@dcs.dataclass_struct(size="native", byteorder="native")  # (2)!
class Vector2d:
    x: dcs.F64  # (3)!
    y: float  #(4)!

@dcs.dataclass_struct(kw_only=True)  #(5)!
class Object:
    position: Vector2d  #(6)!
    velocity: Vector2d = dataclasses.field(  # (7)!
        default_factory=lambda: Vector2d(0, 0)
    )
    name: Annotated[bytes, 8]  #(8)!
  1. This convention of importing dataclasses_struct under the alias dcs is used throughout these docs, but you don't have to follow this if you don't want to.
  2. The size and byteorder keyword arguments control the size, alignment, and endianness of the class' packed binary representation. The default mode "native" is native for both arguments.
  3. A double precision floating point, equivalent to double in C.
  4. The builtin float type is an alias to dcs.F64.
  5. The dataclass_struct decorator supports most of the keyword arguments supported by the stdlib dataclass decorator.
  6. Classes decorated with dcs.dataclass_struct can be used as fields in other dataclass-structs provided they have the same size and byteorder modes.
  7. The stdlib dataclasses.field function can be used for more complex field configurations, such as using a mutable value as a field default.
  8. Fixed-length bytes arrays can be represented by annotating a field with a non-zero positive integer using typing.Annotated. Values longer than the length will be truncated and values shorted will be zero-padded.

Instances of decorated classes have a pack method that returns the packed representation of the object in bytes:

>>> obj = Object(position=Vector2d(1.5, -5.6), name=b"object1")
>>> obj
Object(position=Vector2d(x=1.5, y=-5.6), velocity=Vector2d(x=0, y=0), name=b'object1')
>>> packed = obj.pack()
>>> packed
b'\x00\x00\x00\x00\x00\x00\xf8?ffffff\x16\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00object1\x00'

Decorated classes have an from_packed class method that takes the packed representation and returns an instance of the class:

>>> Object.from_packed(packed)
Object(position=Vector2d(x=1.5, y=-5.6), velocity=Vector2d(x=0.0, y=0.0), name=b'object1\x00')

In size="native" mode, integer type names follow the standard C integer type names:

@dcs.dataclass_struct()
class NativeIntegers:
   c_int: dcs.Int
   c_int_alias: int  # (1)!
   c_unsigned_short: dcs.UnsignedShort
   void_pointer: dcs.Pointer  # (2)!
   size_t: dcs.UnsignedSize

   # etc.
  1. Alias to dcs.Int.
  2. Equivalent to void * pointer in C.

In size="std" mode, integer type names follow the standard fixed-width integer type names in C:

@dcs.dataclass_struct(size="std")
class StdIntegers:
   int8_t: dcs.I8
   int32_t: dcs.I32
   uint64_t: dcs.U64

   # etc.

See the guide for the full list of supported field types.