A fast .xlsx reader/writer with an openpyxl-style API, powered by Rust
Project description
oxlsx
A high-performance Rust library for reading and writing .xlsx files with full formatting support — designed as an openpyxl equivalent in Rust, with Python bindings (PyO3).
5–10x faster than openpyxl on write operations. Streaming reads via
open_readonly().
Status
v0.1.0 released — feature-complete (phases 1–13). First stable API.
Install
Rust
# Cargo.toml
[dependencies]
oxlsx = { git = "https://github.com/alvaroroco/oxlsx", tag = "v0.1.0" }
Published to crates.io on each GitHub release.
Python (build from source via maturin)
Prebuilt abi3 wheels (a single cp38-abi3 wheel per platform, runs on CPython 3.8+) are published to PyPI on each release:
pip install oxlsx
To build from source, use maturin. The wheel build (abi3 extension-module) is configured in pyproject.toml, so no extra flags are needed:
pip install maturin
maturin develop --release # install into the active venv
# or: maturin build --release -> target/wheels/oxlsx-0.1.0-cp38-abi3-*.whl
Contributors running the in-crate PyO3 tests use the interpreter-linked path instead:
cargo test --features python.
| Phase | Scope | Status |
|---|---|---|
| 1 | ZIP parsing, styles, cell.font() / cell.fill() |
✅ Done |
| 2 | Shared strings, Value::String/Bool, cell.text() |
✅ Done |
| 3 | Multi-sheet, Value::Date, openpyxl API parity |
✅ Done |
| 4 | Write support — Workbook::new() + wb.save() |
✅ Done |
| 5 | Lazy / streaming reader (open_readonly()) |
✅ Done |
| 6 | PyO3 Python bindings | ✅ Done |
| 7 | Read-modify-write (open + edit + save) | ✅ Done |
| 8 | Extended write features | ✅ Done |
| 9 | Worksheet iteration API | ✅ Done |
| 10 | Bulk write API | ✅ Done |
| 11 | Rich formatting | ✅ Done |
| 12 | Hyperlinks + plain-text comments | ✅ Done |
| 13 | Freeze panes, print settings, named ranges, tables (lossless RMW) | ✅ Done |
| 14 | Distribution / CI hardening | 🔲 Planned |
| 15 | Pandas engine / tabular adapter | 🔲 Planned |
Usage
Reading
use oxlsx::{Workbook, Value};
fn main() -> Result<(), oxlsx::OxlsxError> {
let wb = Workbook::open("file.xlsx")?;
// Access by index or name
let ws = wb.sheet(0).unwrap();
let ws = wb.sheet_by_name("Employees").unwrap();
println!("Sheets: {:?}", wb.sheetnames());
println!("Active: {}", wb.active().unwrap().title());
if let Some(cell) = ws.cell("A1") {
match cell.value() {
Value::String(s) => println!("text: {s}"),
Value::Number(n) => println!("number: {n}"),
Value::Bool(b) => println!("bool: {b}"),
Value::Date(d) => println!("date: {d}"),
Value::Empty => println!("empty"),
}
if let Some(font) = cell.font() {
println!("bold: {}", font.bold);
}
if let Some(fill) = cell.fill() {
println!("fill: {}", fill.pattern_type);
}
}
Ok(())
}
Writing
use oxlsx::{Color, Fill, Font, Workbook};
fn main() -> Result<(), oxlsx::OxlsxError> {
let mut wb = Workbook::new();
let ws = wb.active_mut().unwrap();
ws.set_cell("A1", "Hello");
ws.set_cell("B1", 42.0_f64);
ws.set_cell("C1", true);
ws.set_cell_font("A1", Font { bold: true, ..Default::default() });
ws.set_cell_fill("A1", Fill {
pattern_type: "solid".to_string(),
fg_color: Some(Color::Argb("FFFF00".to_string())),
bg_color: None,
});
wb.create_sheet("Data").set_cell("A1", "Sheet 2");
wb.save("output.xlsx")?;
Ok(())
}
API (openpyxl parity)
| openpyxl (Python) | oxlsx (Rust) |
|---|---|
load_workbook("f.xlsx") |
Workbook::open("f.xlsx") |
Workbook() |
Workbook::new() |
wb.worksheets |
wb.worksheets() |
wb.sheetnames |
wb.sheetnames() |
wb.active |
wb.active() / wb.active_mut() |
wb["Sheet1"] |
wb.sheet_by_name("Sheet1") |
ws.title |
ws.title() |
ws["A1"] |
ws.cell("A1") |
ws["A1"] = value |
ws.set_cell("A1", value) |
cell.value → str/int/float/date |
cell.value() → &Value |
cell.font.bold |
cell.font()?.bold |
cell.fill.fgColor |
cell.fill()?.fg_color |
cell.comment |
cell.comment() / ws.set_cell_comment(...) |
cell.hyperlink |
cell.hyperlink() / ws.set_cell_hyperlink(...) |
ws.freeze_panes |
ws.freeze_panes() / ws.set_freeze_panes(...) |
ws.print_area |
ws.print_area() / ws.set_print_area(...) |
ws.print_title_rows / print_title_cols |
ws.print_title_rows() / ws.print_title_cols() (+ setters) |
wb.defined_names |
wb.defined_names() (global) / ws.defined_names() (sheet-local) |
ws.add_table(Table(...)) |
ws.add_table(Table::new(...)) |
ws.tables / del ws.tables["T"] |
ws.tables() / ws.remove_table("T") |
wb.save("out.xlsx") |
wb.save("out.xlsx") |
Benchmarks
Measured against openpyxl 3.1.2 on a 10k-row file (5 columns, mixed types). Absolute numbers are machine-dependent; the ratios are what matter. Reproduce with cargo bench + python3 benches/openpyxl_comparison.py.
Write
| Operation | openpyxl | oxlsx | Speedup |
|---|---|---|---|
| Single cell | 1.90ms | 0.18ms | ~10.8x |
| 1k rows × 5 cols | 23.2ms | 3.68ms | ~6.3x |
| 10k rows × 5 cols (50k cells) | 222.4ms | 54.1ms | ~4.1x |
| 3 sheets × 1k rows | 35.9ms | 6.80ms | ~5.3x |
Read (10k rows, 5 cols)
| Operation | openpyxl | oxlsx | Speedup |
|---|---|---|---|
Metadata only (read_only / open_readonly()) |
1.47ms | 0.072ms | ~20x |
| Open + iterate all cells | 151ms (read_only) / 216ms (full) |
33.4ms | ~4.5–6.5x |
Eager open() metadata, 10k |
1.47ms | 28ms | tradeoff: eager loads the whole sheet for random access — use open_readonly() for streaming metadata |
For metadata or large-file streaming, use
open_readonly()(lazy, ~20x faster than openpyxl). Eageropen()loads the full sheet to enable randomws.cell("A1")access.
Run benchmarks yourself:
cargo bench # Rust (criterion)
python3 benches/openpyxl_comparison.py # Python baseline
Data Model
| Type | Description |
|---|---|
Workbook |
Entry point — open or create, access sheets |
Worksheet |
Access / set cells by Excel reference ("A1", "B3") |
Cell |
Holds Value + StyleId + shared Arc<StyleSheet> |
Value |
Empty | String(String) | Number(f64) | Bool(bool) | Date(NaiveDate) | Formula(String) |
StyleSheet |
Registry mapping StyleId → Font + Fill |
StyleId |
Newtype (usize) — prevents index confusion at compile time |
Font |
Bold, italic, size, family, color |
Fill |
Pattern type, foreground/background color |
Color |
Argb(String) | Indexed(u32) | Theme(u32) |
DefinedName |
Named range — name, value (+ comment, hidden); global or sheet-local |
Table |
Worksheet table — display_name, ref, columns, optional TableStyleInfo; lossless RMW for unmodeled features |
Architecture
- SAX only —
quick-xmlevent loop, no DOM, no full-file loading - ZIP direct — reads from the container, no temp disk extraction
Arc<StyleSheet>— shared acrossWorkbook → Worksheet → Cell, no lifetime params- PyO3-ready — all public structs are
'staticcompatible - Newtype pattern —
StyleId(usize)prevents index confusion at compile time
Dependencies
| Crate | Role |
|---|---|
quick-xml |
SAX XML parser + writer |
zip |
OOXML container (deflate only, minimal wheel size) |
thiserror |
Typed error enum |
chrono |
Date resolution (NaiveDate) |
Roadmap
✅ Done
- ZIP container reading + SAX parsing
- Styles: fonts, fills, colors,
StyleIdnewtype - Shared strings →
Value::String -
Value::Bool,Value::Date(resolved at parse time) - Multi-sheet:
wb.sheetnames(),wb.sheet_by_name(),ws.title() - Write support:
Workbook::new(),ws.set_cell(),wb.save() - Style round-trip: bold font + solid fill survive write → open cycle
- Criterion benchmarks + openpyxl comparison script
- Streaming reader (
open_readonly()), PyO3 bindings, read-modify-write - Extended write: formulas, merged cells, column widths, date numFmt
- Iteration API (
iter_rows/iter_cols/rows/columns/values, bounds) - Bulk write (
append, insert/delete rows/cols,move_range) - Rich formatting (
number_format,alignment,border,protection) - Hyperlinks + plain-text comments
- Freeze panes; print area / print titles
- Named ranges —
wb.defined_names()(global) +ws.defined_names()(sheet-local), conservative lossless passthrough - Tables —
ws.add_table(),ws.tables,TableStyleInfo; lossless RMW (unmodeled table features preserved verbatim when untouched)
🔲 Planned
-
Phase 14 — PyPI publish + CI/CD
maturin publishworkflow for PyPI releases- GitHub Actions wheel matrix for Linux, macOS, and Windows
- Version management —
Cargo.toml+pyproject.tomlin sync crates.iopublish for the Rust library
-
Phase 15 — Pandas engine / tabular adapter
engine="oxlsx"integration via a dedicated adapter- DataFrame ↔ XLSX mapping, headers/index handling, and type conversions
- Explicit adapter instead of monkeypatching by default
License
Licensed under either of:
at your option.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oxlsx-0.1.0.tar.gz.
File metadata
- Download URL: oxlsx-0.1.0.tar.gz
- Upload date:
- Size: 285.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2edaa4943908dcb707758fcb45541a3ccaaeee013600210eb3fbcf6d27ac39fd
|
|
| MD5 |
9a248faccebc5d63147641cd0d8716c9
|
|
| BLAKE2b-256 |
f9f6d2e58bcab6fd8865337398da5604a114422100cbf3734e99a164a7a4a051
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0.tar.gz:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0.tar.gz -
Subject digest:
2edaa4943908dcb707758fcb45541a3ccaaeee013600210eb3fbcf6d27ac39fd - Sigstore transparency entry: 1817292856
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file oxlsx-0.1.0-cp38-abi3-win_amd64.whl.
File metadata
- Download URL: oxlsx-0.1.0-cp38-abi3-win_amd64.whl
- Upload date:
- Size: 635.9 kB
- Tags: CPython 3.8+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
440ebea769291f984e01a99cd703d4a0e0afcee1782e99a0ba141afe91f473aa
|
|
| MD5 |
e0577f187bd8a034801df7c8f1db7707
|
|
| BLAKE2b-256 |
a9bbe41a8b7914c1e2cd31fa1a3ea2067bd4efdbfbadeaab9d46f31cb358bac3
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-win_amd64.whl:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0-cp38-abi3-win_amd64.whl -
Subject digest:
440ebea769291f984e01a99cd703d4a0e0afcee1782e99a0ba141afe91f473aa - Sigstore transparency entry: 1817293208
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 809.8 kB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96afd3ba89e2e90483032210c00292ad8f342335e28e6a65fd90e16948a4a8b0
|
|
| MD5 |
bd681f878c26c2fc196d041bd824040a
|
|
| BLAKE2b-256 |
9de2d2f22c58ff8ee7122a347ae0d29f1d116b135c2e2ee19be3188cd9825156
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
96afd3ba89e2e90483032210c00292ad8f342335e28e6a65fd90e16948a4a8b0 - Sigstore transparency entry: 1817293354
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 805.2 kB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c61615ee0b19f2b076dc04c50ae4adee6b2fc1bfcf763a2e3d7215137c760c6a
|
|
| MD5 |
5cfda48dbb08ed788eba4a61f9bfff3d
|
|
| BLAKE2b-256 |
f12cb2ecea14829f6d2334aebd3d07f68ea14547ec480fa842bf589159569e4d
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
c61615ee0b19f2b076dc04c50ae4adee6b2fc1bfcf763a2e3d7215137c760c6a - Sigstore transparency entry: 1817293079
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 733.1 kB
- Tags: CPython 3.8+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ad36b4daff3803fea20abcbfb03300fb3c7dba309dcccd8d9e5000152b7b19e
|
|
| MD5 |
a0d298e07d02bf1839d963603d52f288
|
|
| BLAKE2b-256 |
e4c831514eee255feee93f664d2ebc9957b69db4a0bdf89d2c06c63caa79b836
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl -
Subject digest:
6ad36b4daff3803fea20abcbfb03300fb3c7dba309dcccd8d9e5000152b7b19e - Sigstore transparency entry: 1817293655
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 751.7 kB
- Tags: CPython 3.8+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ca750ab9e1626df13a6bf67a5a12131ef6a8b53bdbbe4af7f3fc7459cf37287
|
|
| MD5 |
bedcf0ad982416ab1e054c24d6f7cc64
|
|
| BLAKE2b-256 |
49de726b3f9e3c783d72733af65808dd34c8ccc262582dc09608453e22377d08
|
Provenance
The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on alvaroroco/oxlsx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl -
Subject digest:
2ca750ab9e1626df13a6bf67a5a12131ef6a8b53bdbbe4af7f3fc7459cf37287 - Sigstore transparency entry: 1817293481
- Sigstore integration time:
-
Permalink:
alvaroroco/oxlsx@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/alvaroroco
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5bb3d55c4df612c6ee5f5bb1394c17e057c44c3d -
Trigger Event:
workflow_dispatch
-
Statement type: