Metadata-Version: 2.4
Name: structed
Version: 0.1.0
Summary: C-style packed binary structs for Python — cross-language friendly
Author-email: Yoav Haimov <haimovyoav@gmail.com>
Project-URL: Homepage, https://github.com/Zikithezikit/structed
Project-URL: Repository, https://github.com/Zikithezikit/structed
Project-URL: Issues, https://github.com/Zikithezikit/structed/issues
Keywords: struct,binary,serialization,c-struct,packed,network
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# structed

C-style packed binary structs for Python, declared with type annotations.
Zero runtime dependencies, byte-for-byte compatible with C `struct`
layouts (including `__attribute__((packed))`), and friendly to
cross-language interop and network protocols.

## Install

```bash
pip install structed
```

## Quick start

```python
from typing import Annotated
from structed import Endian, Struct, uint8_t, uint32_t, char, cstring, cppstring

class Header(Struct, endian=Endian.LITTLE):
    magic:  Annotated[bytes, char[4]]        # fixed-width, NUL-padded
    version: Annotated[int, uint8_t]
    count:  Annotated[int, uint32_t]
    note:   Annotated[str, cstring[16]]      # NUL-terminated C string
    data:   Annotated[str, cppstring]         # length-prefixed string

binary = Header(magic=b"PROT", version=1, count=3, note="hi", data="x").pack()
header = Header.unpack(binary)
obj, rest = Header.unpack_tail(binary + b"more")  # stream framing
```

## Field types

| Marker | Wire type | Python type | Notes |
| --- | --- | --- | --- |
| `uint8_t` .. `uint64_t`, `int8_t` .. `int64_t` | fixed-width int | `int` | byte order follows the struct's `endian` |
| `float32`, `float64` | IEEE-754 | `float` | |
| `char[N]` | `char buf[N]` | `bytes` or `str` | fixed width; pads with NULs |
| `char[N]` + `keep_nulls` | as above | `str` | keeps embedded NULs on unpack |
| `cstring[N]` | C string | `str` or `bytes` | `N` includes the NUL terminator; unpack stops at the first NUL |
| `cppstring` | `u32 length + payload` | `str` or `bytes` | length-prefixed; variable-length |
| `Array(N, T)`, `list[T]`, or `scalar * N` | `T arr[N]` | `list` | fixed-size array of primitives or structs |
| nested struct class | `struct T {...}` | instance | a `Struct` subclass used bare as an annotation |

Annotations use `typing.Annotated`: `Annotated[int, uint16_t]`,
`Annotated[str, cstring[16]]`, `Annotated[str, cppstring]`.

## Arrays

An array packs `N` elements back-to-back and round-trips as a `list`. The
element type is given explicitly, inferred from a `list[T]` annotation, or
repeated with `*`:

```python
class S(Struct, endian=Endian.LITTLE):
    flags: Annotated[list[int], Array(4, uint16_t)]  # explicit
    ages:  Annotated[list[int], uint8_t * 2]          # shorthand for Array(2, uint8_t)
    pts:   Annotated[list[Point], Array(3)]           # array of nested structs

s = S(flags=[1, 2, 3, 4], ages=[12, 13], pts=[...])
assert s.flags == [1, 2, 3, 4] and s.ages == [12, 13]
```

The value must have exactly `N` elements, otherwise packing raises
`PackingError`. Arrays cannot hold variable-length (`cppstring`) elements.

## Struct options

```python
class Packet(Struct, endian=Endian.BIG, packed=True, truncate=False):
    ...
```

- `endian`: `Endian.LITTLE`, `Endian.BIG`, `Endian.NATIVE`, `Endian.NETWORK`.
- `packed`: `True` (default) lays fields back-to-back like C
  `__attribute__((packed))`; `False` applies natural C alignment.
- `truncate`: when `False` (default) a value longer than its `char[N]` /
  `cstring[N]` field raises `PackingError`; when `True` it is silently cut to
  fit (and still NUL-terminated for `cstring`).

The same options are available via the `binary_struct(endian=..., packed=...,
truncate=...)` class decorator.

## Struct methods

- `obj.pack()` -> `bytes`
- `S.unpack(data)` -> `S` (consumes from the front)
- `S.unpack_tail(data)` -> `(S, tail)` for stream framing
- `S.unpack_from(data, offset)` -> `S`
- `S.sizeof()` -> `int` (fixed minimum for variable-length structs)

## Variable-length fields

`cppstring` fields are length-prefixed and may be followed by fixed-size
fields; parsing always resumes right after the payload. Only one variable
field is allowed per struct, `packed=True` is required, and a struct with a
variable field cannot be nested or used as an array element.

## Development

```bash
make venv          # create .venv and install editable + deps
make check         # run tests + syntax + type checks
make example       # run examples/
make build-release # verify, then build sdist + wheel
```
