Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
import "github.com/luxfi/zap"
```

```python
# Python — pure stdlib, no deps. See python/README.md
from zap_py import Builder, parse
```

ZAP is a high-performance binary protocol for AI agent communication and inter-process messaging. It provides **17x faster serialization** and **11x less memory** than MCP JSON-RPC, while maintaining full compatibility with existing MCP tools.

## Features
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/luxfi/mdns v0.1.0 h1:VB3mQcETc9j5SY1S6lAgFtuGr/rjWuDgPYnxS+OKWMQ=
github.com/luxfi/mdns v0.1.0/go.mod h1:/3dheKVjUk2yiS/ocH1IDzeLXOIe+kpVsErIGDOZdiQ=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
Expand Down
5 changes: 5 additions & 0 deletions python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.venv/
__pycache__/
*.pyc
.pytest_cache/
*.egg-info/
117 changes: 117 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# zap_py — Python client for ZAP

Pure-stdlib Python reader and builder for the ZAP wire format. Lets non-Go
peers (AI agents, ops scripts, FHE clients, kcolbchain switchboard/monsoon
agents) speak ZAP without leaving Python.

Wire-compatible with `github.com/luxfi/zap`. Tested in both directions.

## Install

No package install — drop the `zap_py/` directory next to your code, or add
`python/` to `PYTHONPATH`. Python ≥ 3.8, no third-party dependencies.

## Read

```python
from zap_py import parse

msg = parse(wire_bytes)
root = msg.root()
print(root.uint32(0)) # 42
print(root.text(16)) # "from go"
print(root.bytes(24)) # b"\x01\x02\x03\x04"

inner = root.object(32)
print(inner.uint32(0)) # 7

lst = root.list(40)
for i in range(len(lst)):
print(lst.uint32(i))
```

`parse()` accepts `bytes`, `bytearray`, or `memoryview`. The returned
`Message` keeps a `memoryview` over the original buffer — slicing and
`.bytes_view()` stay zero-copy.

## Build

```python
from zap_py import Builder

b = Builder(256)

inner = b.start_object(24)
inner.set_uint32(0, 7)
inner.set_text(8, "nested")
inner_offset = inner.finish()

lb = b.start_list(4)
for v in (10, 20, 30, 40):
lb.add_uint32(v)
list_offset, list_len = lb.finish()

root = b.start_object(56)
root.set_uint32(0, 42)
root.set_uint64(8, 0xDEADBEEFCAFEBABE)
root.set_text(16, "from py")
root.set_bytes(24, b"\x01\x02\x03\x04")
root.set_object(32, inner_offset)
root.set_list(40, list_offset, list_len)
root.finish_as_root()

wire = b.finish() # bytes, ready to send
wire = b.finish_with_flags(0) # explicit flag word
```

Field offsets and data sizes mirror the Go schema exactly — same constants
work on both sides.

## Tests

```bash
cd python
python -m venv .venv && .venv/bin/pip install pytest
.venv/bin/pytest tests
```

12 tests cover: header validation, scalar/text/bytes roundtrip, lists,
nested objects, null pointers, flag bits, invalid magic/version, and
zero-copy `memoryview` access.

## Cross-language interop

Two parity tests confirm wire compatibility against the Go reference:

**Go-built → Python-parsed:**

```bash
go run ./python/testdata/gen_fixture.go > /tmp/zap_fixture.bin
ZAP_GO_FIXTURE=/tmp/zap_fixture.bin python -m pytest python/tests/test_roundtrip.py::test_go_fixture_interop
```

**Python-built → Go-parsed:**

```bash
python python/testdata/gen_python_fixture.py > /tmp/zap_python_fixture.bin
ZAP_PYTHON_FIXTURE=/tmp/zap_python_fixture.bin go test -run TestPythonFixture
```

Both fixtures use identical schemas; the resulting binaries differ only in
their embedded text payload, byte-for-byte.

## Coverage

| Wire feature | Reader | Builder |
|---|---|---|
| Header (magic/version/flags/size) | ✓ | ✓ |
| `bool`, `uint8/16/32/64`, `int8/16/32/64`, `float32/64` | ✓ | ✓ |
| `text`, `bytes` (zero-copy view available) | ✓ | ✓ |
| Nested objects (relative offsets) | ✓ | ✓ |
| Lists of `uint8`/`uint32`/`uint64`/objects/raw bytes | ✓ | ✓ |
| Null object / null list | ✓ | ✓ |

Not yet ported: EVM helpers (`Address`, `Hash`, `Signature`), MCP bridge,
mDNS node, schema DSL. The reader/builder is enough to interop with any
Go ZAP service that publishes a fixed schema. Higher-level helpers can
follow as Python use cases land.
43 changes: 43 additions & 0 deletions python/testdata/gen_fixture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Build a deterministic ZAP message used by the Python interop test.
//
// go run ./python/testdata/gen_fixture.go > /tmp/zap_fixture.bin
// ZAP_GO_FIXTURE=/tmp/zap_fixture.bin pytest python/tests
//
//go:build ignore

package main

import (
"os"

"github.com/luxfi/zap"
)

func main() {
b := zap.NewBuilder(512)

inner := b.StartObject(24)
inner.SetUint32(0, 7)
inner.SetText(8, "nested")
innerOffset := inner.Finish()

lb := b.StartList(4)
lb.AddUint32(10)
lb.AddUint32(20)
lb.AddUint32(30)
lb.AddUint32(40)
listOffset, listLen := lb.Finish()

root := b.StartObject(56)
root.SetUint32(0, 42)
root.SetUint64(8, 0xDEADBEEFCAFEBABE)
root.SetText(16, "from go")
root.SetBytes(24, []byte{0x01, 0x02, 0x03, 0x04})
root.SetObject(32, innerOffset)
root.SetList(40, listOffset, listLen)
root.FinishAsRoot()

if _, err := os.Stdout.Write(b.Finish()); err != nil {
panic(err)
}
}
44 changes: 44 additions & 0 deletions python/testdata/gen_python_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Build a deterministic ZAP message from Python.

Used by python_interop_test.go to confirm the Go reader accepts what
zap_py emits.

python python/testdata/gen_python_fixture.py > /tmp/zap_python_fixture.bin
ZAP_PYTHON_FIXTURE=/tmp/zap_python_fixture.bin go test -run TestPythonFixture
"""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from zap_py import Builder


def build() -> bytes:
b = Builder(512)

inner = b.start_object(24)
inner.set_uint32(0, 7)
inner.set_text(8, "nested")
inner_offset = inner.finish()

lb = b.start_list(4)
for v in (10, 20, 30, 40):
lb.add_uint32(v)
list_offset, list_len = lb.finish()

root = b.start_object(56)
root.set_uint32(0, 42)
root.set_uint64(8, 0xDEADBEEFCAFEBABE)
root.set_text(16, "from py")
root.set_bytes(24, b"\x01\x02\x03\x04")
root.set_object(32, inner_offset)
root.set_list(40, list_offset, list_len)
root.finish_as_root()

return b.finish()


if __name__ == "__main__":
sys.stdout.buffer.write(build())
Empty file added python/tests/__init__.py
Empty file.
Loading