Skip to content

Add Python bindings (Cython, zero-copy)#21

Open
ben-kaye wants to merge 2 commits into
fastserial:mainfrom
ben-kaye:python-bindings
Open

Add Python bindings (Cython, zero-copy)#21
ben-kaye wants to merge 2 commits into
fastserial:mainfrom
ben-kaye:python-bindings

Conversation

@ben-kaye

Copy link
Copy Markdown

Add Python bindings

This adds an optional Python binding under bindings/python/. It's a Cython wrapper around the existing C library, so nothing in the core changes. You read fields directly out of the serialized buffer through lazy dict/list-style proxies (no full copy), and writes are typed.

from lite3 import Lite3

msg = Lite3.from_dict({"event": "ping", "headers": {"id": "req_9f"}})
sock.send(memoryview(msg))         # zero-copy send, no serialize step
got = Lite3.from_bytes(wire)
got["headers"]["id"]               # "req_9f", nested proxy, still no copy
got["hops"] = 1                    # typed write

memoryview(msg) is the wire format.

Why a C shim

Most of the API lives in include/lite3_context_api.h as macros and static inline functions, so it never produces linkable symbols. src/lite3_shim.c includes the headers (macros expand in C) and re-exports them as plain extern functions the Cython layer calls. This is the standard approach and it keeps all the library logic in your C, with nothing duplicated in Python.

Layout

bindings/python/
  lite3/_core.pyx        Cython: Lite3 + _ObjView/_ArrView proxies
  src/lite3_shim.{c,h}   macro-expanding re-export layer
  setup.py / pyproject   compiles ../../src/*.c + ../../lib/** directly
  tests/                 roundtrip + writes, both fuzzed
  README.md              usage + a maintainer's guide

The build compiles the repo's C sources directly (vendored into a gitignored _vendor/ at build time). It doesn't touch the Makefile, so your existing build is untouched.

Notes

  • Purely additive, everything is under bindings/python/.
  • Some hardening is already in: write return codes checked, OverflowError on ints outside i64 range instead of a silent wrap, mutation blocked while a memoryview is exported, stale proxies invalidated after a realloc, and PEP 561 type stubs.
  • Proxies walk raw byte offsets, so a build is tied to the lite3 source it compiles against. This one is built against v1.0.0, commit 8fee994. Cross-version buffer compatibility isn't guaranteed, which matters for the defrag/GC-index and formal-spec roadmap items.
  • bytes only round-trips losslessly through a typed write plus from_bytes. from_dict/to_dict base64 it, since JSON has no bytes type.

Testing

cd bindings/python && pip install .
python tests/test_roundtrip.py
python tests/test_writes.py

ben-kaye added 2 commits June 23, 2026 13:26
Cython extension wrapping the lite3 C library, with packaging
(setup.py/pyproject.toml), tests, and README.
  - Check return codes on all l3_* writes (_chk); raise on buffer-full/overflow
  - Raise OverflowError on ints outside int64 range instead of silent C wrap
  - Block mutation while a memoryview is exported (BufferError)
  - Invalidate stale sub-views after a realloc via a write generation counter;
    views that did the write stay valid
  - Add __repr__/__eq__/__hash__ on Lite3 and cheap reprs on _ObjView/_ArrView
  - Ship PEP 561 stubs (__init__.pyi, py.typed) via pyproject package-data
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant