asun-py
C++ pybind11 extension for ASUN (Array-Schema Unified Notation).
Provides 7 functions without requiring manual schema strings for encoding:
encode, encodeTyped, encodePretty, encodePrettyTyped, decode, encodeBinary, decodeBinary.
The wheel also ships asun.pyi and py.typed, so editors and static type checkers can understand the extension module without a separate stub package.
Requirements
| Tool | Version |
|---|---|
| g++ | ≥ 11 (C++17) |
| python3-dev | any (provides Python.h) |
| Python | ≥ 3.8 |
pybind11 2.13.6 headers are vendored in vendor/pybind11/ — no separate installation needed.
Build
# Option A — shell script (auto-installs python3-dev via sudo if missing)
bash build.sh
# Option B — Makefile
make
# Option C — CMake
cmake -B build && cmake --build build
API
Type inference rules
| Python value | Inferred ASUN type |
|---|---|
bool |
bool |
int |
int |
float |
float |
str |
str |
None |
optional (e.g. str?, int?) |
Cross-row type merging for lists: When encoding a list, all rows are scanned to compute the final type:
- A field that is non-
Nonein row 0 butNonein some later row is promoted to optional (e.g.str→str?,int→int?). - Type conflicts between non-
Nonevalues (e.g.intin row 0,strin row 1) fall back tostr.
This means encodeTyped is safe to use even when only some rows have None for a given field.
encode(obj) -> str — schema without scalar hints, inferred
asun.encode({"id": 1, "name": "Alice"})
# → '{id,name}:\n(1,Alice)\n'
asun.encode([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}])
# → '[{id,name}]:\n(1,Alice),\n(2,Bob)\n'
Decode semantics without scalar hints: When decoded with
decode(), terminal field values are returned as strings because the schema omits scalar type hints. Structural bindings such as@{}and@[]still remain in the schema. UseencodeTypedwhen you need a type-preserving round-trip.
encodeTyped(obj) -> str — schema with scalar hints, inferred
Type is inferred from all rows (not just the first). A field that is None in any row is made optional:
asun.encodeTyped({"id": 1, "name": "Alice", "active": True})
# → '{id@int,name@str,active@bool}:\n(1,Alice,true)\n'
# Optional field inferred from cross-row merging:
asun.encodeTyped([{"id": 1, "tag": "hello"}, {"id": 2, "tag": None}])
# → '[{id@int,tag@str?}]:\n(1,hello),\n(2,)\n'
encodePretty(obj) -> str — pretty + untyped, inferred
pretty = asun.encodePretty(rows)
encodePrettyTyped(obj) -> str — pretty + scalar hints, inferred
pretty = asun.encodePrettyTyped(rows)
decode(text) -> dict | list[dict]
Decodes both schemas with scalar hints and schemas without scalar hints embedded in the text:
# schema with scalar hints → values restored as Python types
rec = asun.decode('{id@int, name@str}:\n(1,Alice)\n') # {'id': 1, 'name': 'Alice'}
rows = asun.decode('[{id@int, name@str}]:\n(1,Alice),\n(2,Bob)\n')
# schema without scalar hints → scalar values returned as strings
rec2 = asun.decode('{id,name}:\n(1,Alice)\n') # {'id': '1', 'name': 'Alice'}
Block comments are supported anywhere whitespace is allowed:
rec = asun.decode('/* top */ {id@int,name@str}: /* row */ (1, /* name */ Alice)')
encodeBinary(obj) -> bytes — schema inferred internally
data = asun.encodeBinary(rows)
decodeBinary(data, schema) -> dict | list[dict]
Schema is required because the binary wire format carries no embedded type information:
rows = asun.decodeBinary(data, "[{id@int, name@str}]")
Typing
asun-py includes inline typing support for the compiled extension:
from asun import decode
rows = decode("[{id@int, name@str}]:(1,Alice),(2,Bob)")
Type checkers will infer dict[str, Any] | list[dict[str, Any]] for decode results and validate function signatures from the bundled asun.pyi.
Binary format
Little-endian layout, identical to asun-rs and asun-go:
| Type | Bytes |
|---|---|
int |
8 (i64 LE) |
uint |
8 (u64 LE) |
float |
8 (f64 LE) |
bool |
1 |
str |
4-byte length LE + UTF-8 bytes |
| optional | 1-byte tag (0=null, 1=present) + value |
| slice | 4-byte count LE + elements |
Run tests
# after building:
python3 -m pytest tests/ -v
Example
import asun
users = [
{"id": 1, "name": "Alice", "score": 9.5},
{"id": 2, "name": "Bob", "score": 7.2},
]
# Schema is inferred automatically—no schema string needed
text = asun.encode(users) # schema binding without scalar hints
textTyped = asun.encodeTyped(users) # schema binding with scalar hints
pretty = asun.encodePrettyTyped(users) # pretty + scalar hints
blob = asun.encodeBinary(users) # binary (schema inferred internally)
assert asun.decode(textTyped) == users # round-trip with scalar hints
assert asun.decode(pretty) == users
assert asun.decodeBinary(blob, "[{id@int, name@str, score@float}]") == users
Latest Benchmarks
Measured on this machine with:
bash build.sh
PYTHONPATH=. python3 examples/bench.py
Headline numbers:
- Flat 1,000-record dataset: ASUN text serialize
118.98msvs JSON403.32ms, deserialize221.21msvs JSON441.89ms - Flat 10,000-record dataset: ASUN text serialize
81.70msvs JSON293.38ms, deserialize158.39msvs JSON317.44ms - Size summary for 1,000 flat records: JSON
137,674 B, ASUN text57,761 B(58%smaller), ASUN binary74,454 B(46%smaller vs JSON) - Throughput summary on 1,000 records: ASUN text was
3.58xfaster than JSON for serialize and2.01xfaster for deserialize - Binary mode was even faster:
7.18xfaster than JSON on serialization and4.16xfaster on deserialization in the benchmark summary
Contributors
Metadata
Release files for asun 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| asun-1.0.1.tar.gz | 201.3 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| asun-1.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.14 | CPython 3.14 | Linux glibc 2.26+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| asun-1.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.26+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| asun-1.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.28+ x86-64, Linux glibc 2.26+ x86-64 | Details |
| asun-1.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.26+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| asun-1.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.10 | CPython 3.10 | Linux glibc 2.26+ x86-64, Linux glibc 2.28+ x86-64 | Details |
| asun-1.0.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.9 | CPython 3.9 | Linux glibc 2.28+ x86-64, Linux glibc 2.26+ x86-64 | Details |
| asun-1.0.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl | CPython 3.8 | CPython 3.8 | Linux glibc 2.26+ x86-64, Linux glibc 2.28+ x86-64 | Details |
Total release size: 1.6 MB
Release files / asun-1.0.1.tar.gz
| Download URL | asun-1.0.1.tar.gz |
|---|---|
| Size | 201.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8b290df9375b4c858c9ee0b62ea1d934458639b1f517799cc2b3f9e65fe9a565
|
|
BLAKE2b-256 checksum How to use checksums |
a5ed9c1cd566655384d6363436caae87b1d28e9ac34c1cc59a3a6fdf1a9b3cc7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 194.8 kB |
| Tags | CPython 3.14 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
35a7036812bced7e325b5db1bbbfd6fc7cd32cb3e40496a33a892285c1095185
|
|
BLAKE2b-256 checksum How to use checksums |
ebbcdc1ae0093778d924e8f04af3e076765673eb35768436021d09b3232965df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 194.7 kB |
| Tags | CPython 3.13 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
8ae037a94a668b0ac21797bd40d3c87c5b5c0250036f9be0d8564f047fdbda10
|
|
BLAKE2b-256 checksum How to use checksums |
caea471eb44b9d7260b012d264892fbb2883edfaf40961267b798c7926773305
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 194.6 kB |
| Tags | CPython 3.12 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
8d8e333a8cb31f108f33325657c003232d0c901890c0bcadeba571deddd2094f
|
|
BLAKE2b-256 checksum How to use checksums |
14b6c397a7a52ffe28c0c07323f76dfe79cff61c4aabd66a561e6f1753c7a6a3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 194.8 kB |
| Tags | CPython 3.11 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
f4eb21bd8367a9f3d6c15d4d4484835d46f4358af2539e326c47e0e3c17b1c61
|
|
BLAKE2b-256 checksum How to use checksums |
7a06fa0fa622b8cd50d43d982a1d08553ca86494a58507f793da304d299e8f89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 193.7 kB |
| Tags | CPython 3.10 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
9297b01a98b6e207ec26af34999db69443458c669192636d83561b290f73721d
|
|
BLAKE2b-256 checksum How to use checksums |
c6af5b98b5a7fafe8bb5d295e369c014e3682b58fe557e0dfb7f5e351d626275
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 194.3 kB |
| Tags | CPython 3.9 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
57363c10d589eb99ec834d8ab39aed9fd745faf466da3114192b5efed9dad1d2
|
|
BLAKE2b-256 checksum How to use checksums |
d7b934acdeab45b07866964055698a1427e98b761bb0a416d7691edcb54d4c44
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / asun-1.0.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
| Download URL | asun-1.0.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 193.5 kB |
| Tags | CPython 3.8 Linux glibc 2.26+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
386745d9d46ad6e6d8c4501e11b2d053b1477efc9dbe1c0b8134ebb449325abc
|
|
BLAKE2b-256 checksum How to use checksums |
e2a3a5e6a3b9c18f1a6adfc081b300724dc713d6d4fe77de20fd8ecf4e1c1ded
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|