Skip to main content

PyStreamXL

PyPI CI Python 3.9+ License: Apache 2.0

Stream large .xlsx files row-by-row in constant memory, 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 flat memory use and faster wall-clock time for streaming iteration.

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 in flat memory (verified 2026-10-04 — see 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); 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.3

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.3
File Size Uploaded Provenance Actions
pystreamxl-6.1.3.tar.gz 85.3 kB Without provenance Details for pystreamxl-6.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pystreamxl 6.1.3
File Interpreter ABI Platform Actions
pystreamxl-6.1.3-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details for pystreamxl-6.1.3-cp311-cp311-macosx_11_0_arm64.whl

Total release size: 1.2 MB

Release files / pystreamxl-6.1.3.tar.gz

Download URL pystreamxl-6.1.3.tar.gz
Size 85.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fa48fc2faa96e72ff0af0ee4eb19bbcc79a5afd418010ca3531bf25c7c3f7ef2
BLAKE2b-256 checksum
How to use checksums
6e7d26f7b561806e43db70d51441e7149241d1c2c73b132f0b9ba2d5e3e150ce
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.3-cp311-cp311-macosx_11_0_arm64.whl

Download URL pystreamxl-6.1.3-cp311-cp311-macosx_11_0_arm64.whl
Size 1.1 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5ef8de7b35482ce96c614be5ad6670f4325a353964cadf4178366bc9ad06caeb
BLAKE2b-256 checksum
How to use checksums
bab6493cd5215b4f3626e34589b46d3e21d573a8352ccf5b1b9d4671c66ec120
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.3

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