Skip to main content

PyStreamXL

PyPI CI Python 3.9+ License: Apache 2.0

Stream large .xlsx files row-by-row with no full-sheet materialization, powered by a Rust core with no unsafe code.

What it is

PyStreamXL reads multi-sheet Excel workbooks without loading them fully into memory, extracts formulas and comments, writes new .xlsx files, and appends to existing ones — all through a small, plain Python API backed by a Rust engine. It installs as pystreamxl and imports as pystreamxl. Compared with openpyxl on large exports, the goal is bounded, row-by-row processing and faster wall-clock time than whole-sheet loaders. Process RSS still grows with allocator retention; see benchmarks/rss_stream.md.

Features / Highlights

  • Streaming reads — read() / stream() return a real Rust iterator; rows are parsed and yielded one at a time, not collected into a Python list up front.
  • Multi-sheet support — sheets(), read_all(), and writer().add_sheet().
  • Streaming writes — write(), writer(), and append() produce real .xlsx files; append() preserves other sheets.
  • Formula extraction — formula text plus a best-effort formula-type classification (with_formulas=True), FormulaReferenceMapper for shifting/rewriting references, and FormulaSerializer for exporting/importing formulas as JSON or CSV.
  • Comment extraction — cell comments and authors, via with_formulas=True.
  • Conditional formatting — conditional_formats() reads every <conditionalFormatting>/<cfRule> (type, operator, formulas, priority, stopIfTrue) and resolves dxfId against xl/styles.xml into concrete font/fill values.
  • Type-aware cells — strings, numbers, booleans, dates, datetimes, and empty cells round-trip correctly.
  • Error recovery & validation — validate_excel_file() and ErrorRecoveryHandler classify and optionally recover from malformed cells instead of hard-failing.
  • Security hardening — path validation, file-size limits, and ZIP-bomb defenses enforced before/while a file is opened; CSV export is sanitized against formula-injection.
  • Optional REST API — pystreamxl.server (requires pystreamxl[server]) wraps the streaming engine behind HTTP endpoints. No authentication — see limitations.

Who it's for / Use cases

  • ETL against large Excel exports that don't fit comfortably in memory with openpyxl — read() keeps memory flat regardless of file size.
  • Extracting formulas and comments for audit or migration tooling, not just cell values.
  • Appending to a growing log-style .xlsx file without rewriting the whole workbook or losing other sheets.
  • Data engineering pipelines that need a fast, dependency-light reader for .xlsx with an optional HTTP surface.

What it is not

  • No SQL-style query language. execute_query() in the REST API streams rows from a named sheet; it does not parse arbitrary queries.
  • No pandas/Parquet/Arrow export built in. Convert read()'s output yourself.
  • No formula evaluation. Formula text is extracted and classified, not recalculated.
  • The pystreamxl dashboard CLI renders sample data, not live telemetry. Every mode prints an explicit "SAMPLE DATA — not live" warning.
  • The optional REST server has no authentication. PyStreamXLServer / create_flask_app() have no login, API key, or token check — anyone who can reach the port can read and query every loaded source. Keep it on localhost or behind your own auth layer (reverse proxy, API gateway).
  • Prebuilt wheels are macOS arm64 only. Every PyPI release has shipped exactly one platform wheel plus an sdist; Linux/Windows/x86_64-mac users build from source, which requires a Rust toolchain and maturin.
  • Some listed release features are not reachable from Python. ~1,180 lines of Rust (collaboration detection, incremental recalculation, cross-sheet dependency analysis) compile and pass their own tests but are not referenced by python/src/lib.rs; see ROADMAP_HONEST.md.
  • Not all conditional-formatting detail is modeled. colorScale/dataBar/iconSet rules are captured at the type/sqref/priority level only; their inline color-stop and threshold definitions are not represented.

Project status

Production/stable core, with disclosed gaps. Current version 6.1.2 (Development Status :: 5 - Production/Stable). The read/write/stream engine is covered by the Rust and Python test suites and streams row-by-row without materializing the whole sheet (see benchmarks/rss_stream.md for process RSS evidence — ROADMAP_HONEST.md gap #10). The Production/Stable classifier is broader than the test coverage of every optional surface; the honest technical-debt list lives in ROADMAP_HONEST.md.

Installation

pip install pystreamxl

A prebuilt wheel is currently published only for macOS (arm64). Other platforms install from the source distribution, which requires a Rust toolchain (see rust-toolchain.toml) and maturin.

Requires Python 3.9+. The REST server extra:

pip install "pystreamxl[server]"

Quick start

import pystreamxl

for row in pystreamxl.read("data.xlsx"):
    print(row)  # ['Name', 'Age', 'Score']

Usage

Read as dictionaries, keyed by header row:

for row in pystreamxl.read("sales.xlsx", as_dict=True):
    print(row["Customer"], row["Amount"])

Read only specific columns:

for row in pystreamxl.read("sales.xlsx", as_dict=True, columns=["Customer", "Amount"]):
    ...

Read every sheet in a workbook:

sheet_names = pystreamxl.sheets("workbook.xlsx")
all_data = pystreamxl.read_all("workbook.xlsx")  # {sheet_name: [rows...]}

Write a new .xlsx file:

import datetime
import pystreamxl

pystreamxl.write("report.xlsx", [
    ["Name", "Joined", "Score"],
    ["Alice", datetime.date(2024, 1, 15), 95.5],
    ["Bob", datetime.date(2024, 3, 2), 88.0],
])

Stream-write multiple sheets without holding the whole file in memory:

with pystreamxl.writer("report.xlsx") as w:
    w.write_row(["Name", "Age"])
    w.write_row(["Alice", 30])
    w.add_sheet("Summary")
    w.write_row(["Total", 1])

Append rows to an existing file (other sheets are preserved):

pystreamxl.write("log.xlsx", [["Date", "Event"]])
pystreamxl.append("log.xlsx", [[datetime.date.today(), "started"]])
pystreamxl.append("log.xlsx", [[datetime.date.today(), "finished"]])

Extract formulas and comments:

from pystreamxl import FormulaSerializer

rows = list(pystreamxl.read("model.xlsx", with_formulas=True))
# each cell is a dict: {"value": ..., "formula": ..., "formula_type": ...,
#                        "comment": ..., "comment_author": ...}
FormulaSerializer.export_to_json(rows, "formulas.json")
FormulaSerializer.export_to_csv(rows, "formulas.csv")  # sanitized against CSV/formula injection

Export to CSV safely — untrusted cell content is never written verbatim:

import csv
import pystreamxl
from pystreamxl.security import sanitize_csv_cell

with open("output.csv", "w", newline="") as f:
    writer = csv.writer(f)
    for row in pystreamxl.read("large.xlsx"):
        writer.writerow([sanitize_csv_cell(cell) for cell in row])

Validate a file and recover from bad cells instead of crashing:

from pystreamxl import validate_excel_file

report = validate_excel_file("questionable.xlsx")
if report.has_fatal_errors():
    print(report.format_summary())

CLI:

pystreamxl dashboard          # sample extraction dashboard
pystreamxl dashboard --static # same sample data, clearly labeled "SAMPLE DATA — not live"
pystreamxl --version

Security

  • Path & size validation — validate_read_path() / validate_write_path() reject non-.xlsx paths, path traversal (when a base_dir is supplied), and oversized files before parsing.
  • ZIP-bomb defenses — per-entry size limit, compression-ratio limit, and total decompressed-size limit enforced by the Rust core (core/src/zip_reader.rs).
  • CSV / formula-injection protection — pystreamxl.security.sanitize_csv_cell() neutralizes string cells starting with =, +, -, @, TAB, or CR.
  • Known gap: the optional REST server has no authentication.

Enforced limits (by default): max file size 512 MB, max size per ZIP entry 512 MB, max total decompressed size 1 GB, max compression ratio 30:1. Handle malicious files with:

from pystreamxl import SecurityError, read

try:
    for row in read("data.xlsx"):
        process(row)
except SecurityError as e:
    print(f"Security violation: {e}")

Found a security issue? See SECURITY.md.

Documentation

Rows are yielded one at a time, and memory stays flat during iteration regardless of sheet size (fixed 2026-10-04; see ROADMAP_HONEST.md gap #10). The pyproject.toml description cites 3.8–5.6x faster than openpyxl in this repo's committed benchmarks (benchmarks/results.md). That is an openpyxl baseline: python-calamine is much faster at whole-sheet loads (see benchmarks/calamine_vs_pystreamxl.md), so choose PyStreamXL for bounded-memory streaming, not raw speed. Actual numbers depend heavily on file structure, so measure on your own workloads rather than trusting a generic table.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a PR. See CODE_OF_CONDUCT.md for community expectations.

git clone https://github.com/Mullassery/PyStreamXL.git
cd PyStreamXL
pip install -e ".[dev]"        # builds the Rust extension via maturin and installs test deps
pytest tests/ -v
cargo test --release --all-features

License

This project is licensed under the Apache License 2.0.

Metadata

Release files for pystreamxl 6.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pystreamxl 6.1.7
File Size Uploaded Provenance Actions
pystreamxl-6.1.7.tar.gz 90.3 kB Without provenance Details for pystreamxl-6.1.7.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for pystreamxl 6.1.7
File Actions
pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64 Details for pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ ARM64 Details for pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
pystreamxl-6.1.7-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details for pystreamxl-6.1.7-cp312-cp312-macosx_11_0_arm64.whl
pystreamxl-6.1.7-cp312-cp312-macosx_10_12_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.12+ x86-64 Details for pystreamxl-6.1.7-cp312-cp312-macosx_10_12_x86_64.whl

Total release size: 5.2 MB

Release files / pystreamxl-6.1.7.tar.gz

Download URL pystreamxl-6.1.7.tar.gz
Size 90.3 kB
Tags Source
SHA-256 checksum
How to use checksums
753c0fde487d90b216b5a4fd2f607245d37586889af8f5d2c6b671daec7500b7
BLAKE2b-256 checksum
How to use checksums
096f486cbbda6602e2eeddd03700e7d77e2666e4209e57359bfa9fb69e9e59d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.3 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
8b6ead822f4b6359516b5d0df0b12442f78655d5a32aea7529674da5b55ec3e3
BLAKE2b-256 checksum
How to use checksums
ca12b4afc039af79d31a5e38bf1e7aaeecb5384bb224c638364379e43f37e78f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pystreamxl-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.2 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
dd490821a1f01f534bf3dfb481a0959228303b11e2588f274fd9bd415fb47cb4
BLAKE2b-256 checksum
How to use checksums
8e7f3a377c5ffa6b9a338fb6888c6864d60eafbab737f5fa1ae5e11d9559c0b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / pystreamxl-6.1.7-cp312-cp312-macosx_11_0_arm64.whl

Download URL pystreamxl-6.1.7-cp312-cp312-macosx_11_0_arm64.whl
Size 1.2 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
d28986db6b861f6c512de2b6a843505ba6afb973aa3a3624c30d7688c1647d04
BLAKE2b-256 checksum
How to use checksums
843c5e69732e2073bb76f71822e3a8f573bd75ce0ffe52b2e78e7041b3105804
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / pystreamxl-6.1.7-cp312-cp312-macosx_10_12_x86_64.whl

Download URL pystreamxl-6.1.7-cp312-cp312-macosx_10_12_x86_64.whl
Size 1.3 MB
Tags CPython 3.12 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
932100137a70e58f8e63a31fd819118a3d40e2d76d24ac0c26e9390bbfd67527
BLAKE2b-256 checksum
How to use checksums
21e12a8c8c7eac960748ebd98dc3e7a17ce6b90f8d99a03602050ee895afbb70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Security for pystreamxl 6.1.7

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page