euspinolia
A small, educational CSV/table library with its engine in Zig and a thin
ctypes layer in Python. It parses a CSV into typed columns, reduces,
filters, sorts and groups them, and writes them back out — faster than
pandas for most of that, with no dependencies and a shared library that
links nothing, not even libc.
The name comes from Euspinolia, the genus of the velvet ant known as the "panda ant".
>>> import euspinolia
>>> df = euspinolia.read_csv("people.csv")
>>> df
name age score city
0 ada 36 91.5 London
1 grace 45 88.0 New York
2 Doe, John 29 73.25 Paris
[3 rows x 4 columns]
>>> df["age"].to_list()
[36, 45, 29]
>>> df[df["age"] > 30]
name age score city
0 ada 36 91.5 London
1 grace 45 88.0 New York
[2 rows x 4 columns]
>>> df.sort_values("score", ascending=False)[["name", "score"]]
name score
0 ada 91.5
1 grace 88.0
2 Doe, John 73.25
[3 rows x 2 columns]
>>> df.groupby("city").agg({"score": "mean"})
city score
0 London 91.5
1 New York 88.0
2 Paris 73.25
[3 rows x 2 columns]
Install
pip install euspinolia
Wheels are published for Linux (x86_64, aarch64, armv7l), macOS (x86_64,
arm64) and Windows (x64, arm64), for any Python 3.9 or newer. On anything else,
pip builds from source; that needs no Zig install either, since the build
pulls it from the ziglang package.
Then take a first look at a file without writing any Python:
$ euspinolia stats data.csv
data.csv: 500,000 rows x 5 columns, 18.0 MB, parsed in 108 ms
column type min max mean distinct
------ ------ ----- ------ ---------- --------
id int 0 499999 249999.5 500,000
name string 500,000
dept string 5
salary int 40000 199999 119926.142 152,970
score float 0.0 100.0 49.973 10,001
euspinolia head data.csv -n 20 prints the first rows, and both take
--delimiter (';', '\t'). python -m euspinolia works too.
To work from a checkout instead, see docs/development.md.
What it does
import euspinolia
df = euspinolia.read_csv("people.csv") # parse a file
df = euspinolia.read_csv("people.tsv", delimiter="\t") # or TSV, or ';'
df = euspinolia.parse_csv(csv_text) # parse text already in memory
df = euspinolia.from_dict({"name": ["ada"], "age": [36]}) # or build it from Python
df.shape # (3, 4) — (rows, columns)
df.columns # ('name', 'age', 'score', 'city')
df.dtypes # (string, int, float, string)
df.head(2) # the first two rows as tuples
column = df["age"] # by name; df[1] works too
column[0] # 36 — read straight out of the Zig buffer
column.to_list() # [36, 45, 29]
column.sum() # 110 — exact, reduced in Zig
column.mean() # 36.666666666666664
column.min() # 29
column.max() # 45
df[["name", "age"]] # a new DataFrame with those columns
df[df["age"] > 30] # a new DataFrame with the matching rows
df[(df["age"] > 30) & (df["score"] < 90)] # conditions chain with &
df.filter("city", "==", "Paris") # the same thing, as a call
df.sort_values("age", ascending=False) # rows ordered by one column, stably
df.groupby("city").agg({"score": "mean", "age": "max"}) # one row per city
df.groupby("city").sum() # every numeric column
df.groupby("city").count() # rows per group
df.to_csv("out.csv") # write it back out; reads back as the same frame
df.to_csv() # or as a string
df.to_dict() # or as {"name": [...], "age": [...]}
with euspinolia.read_csv("people.csv") as df:
... # Zig-side memory freed on the way out; df.close() does the same
Every column has one type — int, float or string — inferred from
its values. Reductions keep that type, so an integer column sums exactly.
A selected, filtered, sorted or grouped frame owns its own memory and
outlives its source.
Errors are ordinary Python exceptions: FileNotFoundError, ParseError,
TypeError for text where a number was needed, OverflowError for a sum
that leaves 64 bits.
The full reference is in docs/api.md.
Performance
500,000 rows, 5 columns, 18 MB; best of five on one laptop, against pandas
3.0 and the standard csv module:
| euspinolia | pandas | csv module + Python | |
|---|---|---|---|
| read + parse | 114 ms | 183 ms | 335 ms |
write back out (to_csv) |
63 ms | 478 ms | 294 ms |
| sum an int column | 0.2 ms | 0.2 ms | 10.1 ms |
| filter, keeping half the rows | 10.5 ms | 8.7 ms | 13.8 ms |
| sort by an int column | 40.7 ms | 69.8 ms | 110.6 ms |
| groupby (5 groups), mean | 7.5 ms | 23.6 ms | 47.8 ms |
Parsing, writing, sorting and grouping are ahead of pandas — sorting because numeric keys go through a radix sort. Reductions are a wash, as native code against native code should be; filtering is the one loss, because the result copies its strings rather than sharing them. What each number means, and how to run the benchmark yourself, is in docs/benchmarks.md.
How it works
[Python] df = euspinolia.read_csv("data.csv")
│ ctypes call
▼
[Zig] CSV scanner → type inference → one typed array per column
│
├─▶ sum / mean / min / max → one value
├─▶ select, filter, sort,
│ groupby → a new frame
└─▶ to_csv → bytes
│ opaque handle + borrowed column pointers
▼
[Python] DataFrame / Column wrap the handle; df["age"][0] reads the
Zig buffer through ctypes, no copy
Columns are struct-of-arrays: int and float are flat []i64 / []f64,
strings are one packed byte buffer plus offsets. A filter or an aggregate
walks one contiguous array; a groupby hashes each key into a dense group id
and folds into a flat accumulator; Python reads numeric columns in place.
Module by module: docs/internals.md.
Scope
This is a teaching project, and the scope was fixed at the start: one delimiter per file, one header row, three column types, and the operations above. Multi-index, date/time types, NaN semantics, join/merge and pivot tables are out, on purpose. The goal is not a real table engine but a subset that genuinely works, is fast for real reasons, and can be read end to end in an afternoon — about 3,700 lines of Zig, tests included, and 1,250 of Python.
Documentation
- API reference — every function, method, argument and exception
- Internals — the Zig side, module by module, and the ABI
- Benchmarks — the numbers above, what they measure and why they come out that way
- Development — building, testing, cross-compiling wheels, releasing
- Changelog
License
MIT.
Metadata
Release files for euspinolia 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| euspinolia-0.2.0.tar.gz | 74.4 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| euspinolia-0.2.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| euspinolia-0.2.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| euspinolia-0.2.0-py3-none-manylinux2014_x86_64.musllinux_1_1_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64, Linux musl 1.1+ x86-64 | Details |
| euspinolia-0.2.0-py3-none-manylinux2014_armv7l.musllinux_1_1_armv7l.whl | Python 3 | none | Linux musl 1.1+ ARMv7l, Linux glibc 2.17+ ARMv7l | Details |
| euspinolia-0.2.0-py3-none-manylinux2014_aarch64.musllinux_1_1_aarch64.whl | Python 3 | none | Linux musl 1.1+ ARM64, Linux glibc 2.17+ ARM64 | Details |
| euspinolia-0.2.0-py3-none-macosx_11_0_x86_64.whl | Python 3 | none | macOS 11.0+ x86-64 | Details |
| euspinolia-0.2.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 1.4 MB
Release files / euspinolia-0.2.0.tar.gz
| Download URL | euspinolia-0.2.0.tar.gz |
|---|---|
| Size | 74.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0960a5bbb16cad0bee8fc788085bec383bca06a988b2030a1ca79065364e7988
|
|
BLAKE2b-256 checksum How to use checksums |
30baaa117a77da8ddf8475eebeb8d1655c061fd2f79a87fc3280936edf8d85e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-win_arm64.whl
| Download URL | euspinolia-0.2.0-py3-none-win_arm64.whl |
|---|---|
| Size | 191.6 kB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
e2833bc78cbb28d308f6ed985e0f1edf11ec18e5bed13d1821702f2991257857
|
|
BLAKE2b-256 checksum How to use checksums |
fd95cfa0160ae461ddb5a069c2f439f6b24d1149c8cfaaa7c771d126dffd3789
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-win_amd64.whl
| Download URL | euspinolia-0.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 230.4 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
77eb6848849ad11c58c018c4146971a562680e4935f2938a6a012de2af7d7a98
|
|
BLAKE2b-256 checksum How to use checksums |
6e7c490c7a50022cdc324d05b34301bad71bcfd28be61d35124225abcd3a695c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-manylinux2014_x86_64.musllinux_1_1_x86_64.whl
| Download URL | euspinolia-0.2.0-py3-none-manylinux2014_x86_64.musllinux_1_1_x86_64.whl |
|---|---|
| Size | 174.0 kB |
| Tags | Linux glibc 2.17+ x86-64 Linux musl 1.1+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
2605677765653b946786d623d6b9ae3d2082f6208f30a19f1fa376cbb767ca94
|
|
BLAKE2b-256 checksum How to use checksums |
f88622e811ec516c027b8b9c92560cd78735ca0870ea57e876f64245bdc07ccb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-manylinux2014_armv7l.musllinux_1_1_armv7l.whl
| Download URL | euspinolia-0.2.0-py3-none-manylinux2014_armv7l.musllinux_1_1_armv7l.whl |
|---|---|
| Size | 179.4 kB |
| Tags | Linux glibc 2.17+ ARMv7l Linux musl 1.1+ ARMv7l Python 3 |
|
SHA-256 checksum How to use checksums |
07dda5acfc9418c5eabd3537ae9499e6977a5e15e95a8a1a6e306ac7c52f94c9
|
|
BLAKE2b-256 checksum How to use checksums |
1cf41d2774167b45f961a219d4453e37c0daa25c1a5c060710926d2c17c85c15
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-manylinux2014_aarch64.musllinux_1_1_aarch64.whl
| Download URL | euspinolia-0.2.0-py3-none-manylinux2014_aarch64.musllinux_1_1_aarch64.whl |
|---|---|
| Size | 174.3 kB |
| Tags | Linux glibc 2.17+ ARM64 Linux musl 1.1+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
dfe10b2293c1f4c93d12e6cada4393198ea82153eb103c1b4ac45f112867ef9c
|
|
BLAKE2b-256 checksum How to use checksums |
00f55cf6d39e2c6bc29c9dcaaa1276d42cd9a88bd2e4d1b248cdbfe99db00acb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-macosx_11_0_x86_64.whl
| Download URL | euspinolia-0.2.0-py3-none-macosx_11_0_x86_64.whl |
|---|---|
| Size | 178.2 kB |
| Tags | Python 3 macOS 11.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
9f9c96080aca667673725f383d7efbd013dc255ee344387f5059035a5572dfee
|
|
BLAKE2b-256 checksum How to use checksums |
df071d1433c8447ac0d1cfa7a7c29797d62340aa2b5a841759430bc0975ac6bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / euspinolia-0.2.0-py3-none-macosx_11_0_arm64.whl
| Download URL | euspinolia-0.2.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 160.6 kB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8bcae4630e95f9bfe78fca66410ca50691fb6da107fc206c81fd5a5f06cbb7b9
|
|
BLAKE2b-256 checksum How to use checksums |
ff69c01533c21d47d0613cfb84ad174d13ed0063f8781d8d1c6a7b95d1981021
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log