streamval
Streaming, Pydantic-backed validation for CSV, JSONL, Parquet, Arrow, and HTTP NDJSON / SSE.
Existing data-validation libraries (Pydantic, Pandera, Great Expectations,
Cerberus) all assume the dataset fits in memory. streamval keeps the
file (or HTTP response) on disk / on the wire and validates it row by
row through a Pydantic schema, so you can validate a multi-gigabyte
file with a few tens of megabytes of RAM and start consuming valid
rows immediately. The same streaming model handles LLM token streams,
log services, and any REST endpoint that emits NDJSON or Server-Sent
Events.
Install
pip install streamval
# faster JSON + lazy CSV via polars/orjson:
pip install "streamval[fast]"
# HTTP NDJSON / LLM streaming via httpx:
pip install "streamval[http]"
# everything:
pip install "streamval[fast,http]"
Quickstart
from pydantic import BaseModel
from streamval import stream_csv
class User(BaseModel):
id: int
name: str
score: float
active: bool
for result in stream_csv("users.csv", User, on_error="collect"):
if result.valid:
user = result.data
# ... do something with the parsed model ...
else:
for err in result.errors:
print(f"row {result.row_index}: {err}")
The generator finishes when the file ends. Stats are available on the underlying validator:
from streamval import StreamValidator
v = StreamValidator(User, on_error="skip", batch_size=2000)
for r in v.stream_csv("users.csv"):
handle(r.data)
print(v.stats) # rows_total, rows_valid, throughput_rps, peak_memory_mb, ...
Performance
streamval optimises for bounded memory with strong throughput as
a secondary goal. Throughput, 100 000 rows, 4-column schema:
| Mode | rows/sec | Peak memory, 1M rows |
|---|---|---|
| streamval Parquet — batch (Arrow path) | ~112 000 | bounded by batch_size |
| streamval CSV — row mode | ~100 000 | 0.5 MB @ batch_size=1000 |
| streamval CSV — batch (Arrow path) | ~88 000 | 1.9 MB @ batch_size=1000 |
| Naive Pydantic loop | ~160 000 | ~1 GB (reads whole file) |
On the naive loop: it stays the fastest option for files that fit comfortably in RAM, because
streamvaldoes strictly more work per row — it allocates aValidationResultcarrying the row index, the raw row, and any field errors. That per-row object is the feature.streamvalis the right choice when files don't fit in memory, when you want valid rows immediately, or when you need per-row error reporting rather than a single exception.
Numbers from a developer Windows laptop with Python 3.13, pydantic 2.11, polars 1.40. Run
STREAMVAL_BENCH=1 pytest tests/benchmarks/to measure on your own machine.
Performance tuning
-
Leave
track_memory=False(the default). It gatestracemalloc, which hooks every allocation and costs roughly 4-5× throughput. Turn it on only for profiling runs — it is what populatesstats.peak_memory_mb, which reads0.0while it is off. -
Install
streamval[fast]to unlock the polars path for CSV. Parquet gets the Arrow fast path with no extra dependency. On the sync path polars is roughly on par with the stdlibcsvreader for simple schemas — measure before adding the dependency for CSV alone. -
use_arrow=Trueis the default for CSV and Parquet, and is a clear win for Parquet. For CSV the two modes now measure within a few percent of each other; row mode holds a small edge on peak memory at the samebatch_size. Benchmark both on your own data. -
CSV batch mode casts whole columns in Arrow rather than converting each cell in Python. Columns that parse cleanly take the vectorised path; a column containing even one unparseable cell falls back to per-row coercion, so results are identical either way — only the speed differs. Common formatting variants are handled column-wise and stay fast: padded cells (
" 42 "), worded booleans (yes/no/y/n/t/f), and integers written as floats ("42.0"). ISO dates are required for the fast path; other date formats still validate, just per row. -
batch_sizeis the main throughput / memory dial — larger batches mean fewer Python ↔ Rust crossings but proportionally higher peak memory. Measured peak on 1M rows, Arrow batch mode:batch_size=100 → ~0.3 MB live, but see below batch_size=1000 → ~1.9 MB peak (default) batch_size=5000 → ~9 MB peak batch_size=10000 → ~16 MB peakFrom
batch_size=1000upward these reproduce to within 0.01 MB across machines. Below that they do not. Once the live working set drops under ~1 MB, the peak is dominated by however much short-lived garbage happens to be uncollected when the high-water mark is taken, which depends on GC timing rather than onbatch_size: atbatch_size=100the same code has measured anywhere from 0.26 MB to 8.2 MB across machines and runs. Preferbatch_size=1000unless you have measured a smaller value on your hardware and workload. -
workers > 1enables a thread pool. Pydantic's Rust core is thread-safe; per-row ordering is preserved.
Formats
| Format | Source | Requires |
|---|---|---|
| CSV | file / path | (none, or streamval[fast] for polars path) |
| JSONL | file / path | (none, or streamval[fast] for orjson) |
| Parquet | file / path | pyarrow (always-on dependency) |
| Arrow | file / path | pyarrow (always-on dependency) |
| NDJSON | HTTP URL | streamval[http] (httpx) |
| SSE/LLM | HTTP URL | streamval[http] (httpx) |
Why not Pydantic / Pandera / Great Expectations?
| Library | Loads whole file? | Streams? | Multi-format? | Async? |
|---|---|---|---|---|
| Pydantic v2 | yes (caller decides) | no | no | no |
| Pandera | yes (DataFrame) | no | DataFrame only | no |
| Great Expectations | yes (DataFrame) | no | DataFrame only | no |
| Cerberus | per-record only | no | no | no |
| streamval | no | yes | CSV / JSONL / Parquet / Arrow / HTTP NDJSON / SSE | yes |
How it works
- Each format has a tiny async-generator adapter that yields one row dict at a time without loading the whole file.
- A
BatchBufferchunks the row stream into fixed-size lists so peak memory stays bounded bybatch_size. - Each batch is run through a
CompiledValidationPlan(a per-model, cached wrapper aroundmodel.model_validate). - A pluggable error strategy (
fail_fast,collect,skip) decides whether each row is emitted, dropped, or terminates the run. - A
StatsAccumulatorrecords per-field error counts, throughput, and peak memory viatracemalloc.
Error strategies
fail_fast— raiseStreamValidationErroron the first invalid row.collect— emit every row; ifmax_errorsis exceeded, raise on finalize.skip— drop invalid rows silently (logged at WARNING level).
Documentation
Online docs: streamval.readthedocs.io
Full guides (also in docs/):
| Guide | Topics |
|---|---|
| Documentation index | End-to-end workflow, install, doc map |
| Getting started | Quickstart and schema design |
| User guide | Validation, results, error strategies |
| Reference | Adapters, API, Python docstrings |
| Development | Benchmarks and changelog |
Contributing
git clone https://github.com/AmeerTechsoft/streamval
cd streamval
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE.
Metadata
Release files for streamval 0.3.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 | Provenance | Actions |
|---|---|---|---|---|
| streamval-0.3.1.tar.gz | 92.4 kB | With provenance | Details for streamval-0.3.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| streamval-0.3.1-py3-none-any.whl | Python 3 | none | any | Details for streamval-0.3.1-py3-none-any.whl |
Total release size: 138.9 kB
Release files / streamval-0.3.1.tar.gz
| Download URL | streamval-0.3.1.tar.gz |
|---|---|
| Size | 92.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
80fcea158e3b0432d2082824475923267c8ca57985940141e5a37ada44c8fe3b
|
|
BLAKE2b-256 checksum How to use checksums |
d6a761ac2669f053d68ab77e2ae7ce88ba34ca08d7d9e6975592a0def1e27320
|
| 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 Aug 12, 2026.
Transparency logRelease files / streamval-0.3.1-py3-none-any.whl
| Download URL | streamval-0.3.1-py3-none-any.whl |
|---|---|
| Size | 46.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f171f6c5d295ba18ef868d835b9d4ecf7fcd1cb272d50291dea04bcdb56d7015
|
|
BLAKE2b-256 checksum How to use checksums |
fa87405f0e30fd27c377679da881534437b90de15456b758b7ee8a032b965f2f
|
| 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 Aug 12, 2026.
Transparency logSecurity for streamval 0.3.1
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for streamval-0.3.1.tar.gz
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 Aug 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.