Skip to main content

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.valuestr/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). Eager open() loads the full sheet to enable random ws.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 StyleIdFont + 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 onlyquick-xml event loop, no DOM, no full-file loading
  • ZIP direct — reads from the container, no temp disk extraction
  • Arc<StyleSheet> — shared across Workbook → Worksheet → Cell, no lifetime params
  • PyO3-ready — all public structs are 'static compatible
  • Newtype patternStyleId(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, StyleId newtype
  • 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 publish workflow for PyPI releases
    • GitHub Actions wheel matrix for Linux, macOS, and Windows
    • Version management — Cargo.toml + pyproject.toml in sync
    • crates.io publish 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oxlsx-0.1.0.tar.gz (285.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

oxlsx-0.1.0-cp38-abi3-win_amd64.whl (635.9 kB view details)

Uploaded CPython 3.8+Windows x86-64

oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (809.8 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (805.2 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl (733.1 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl (751.7 kB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

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

Hashes for oxlsx-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2edaa4943908dcb707758fcb45541a3ccaaeee013600210eb3fbcf6d27ac39fd
MD5 9a248faccebc5d63147641cd0d8716c9
BLAKE2b-256 f9f6d2e58bcab6fd8865337398da5604a114422100cbf3734e99a164a7a4a051

See more details on using hashes here.

Provenance

The following attestation bundles were made for oxlsx-0.1.0.tar.gz:

Publisher: release.yml on alvaroroco/oxlsx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for oxlsx-0.1.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 440ebea769291f984e01a99cd703d4a0e0afcee1782e99a0ba141afe91f473aa
MD5 e0577f187bd8a034801df7c8f1db7707
BLAKE2b-256 a9bbe41a8b7914c1e2cd31fa1a3ea2067bd4efdbfbadeaab9d46f31cb358bac3

See more details on using hashes here.

Provenance

The following attestation bundles were made for oxlsx-0.1.0-cp38-abi3-win_amd64.whl:

Publisher: release.yml on alvaroroco/oxlsx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for oxlsx-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 96afd3ba89e2e90483032210c00292ad8f342335e28e6a65fd90e16948a4a8b0
MD5 bd681f878c26c2fc196d041bd824040a
BLAKE2b-256 9de2d2f22c58ff8ee7122a347ae0d29f1d116b135c2e2ee19be3188cd9825156

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for oxlsx-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 c61615ee0b19f2b076dc04c50ae4adee6b2fc1bfcf763a2e3d7215137c760c6a
MD5 5cfda48dbb08ed788eba4a61f9bfff3d
BLAKE2b-256 f12cb2ecea14829f6d2334aebd3d07f68ea14547ec480fa842bf589159569e4d

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for oxlsx-0.1.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6ad36b4daff3803fea20abcbfb03300fb3c7dba309dcccd8d9e5000152b7b19e
MD5 a0d298e07d02bf1839d963603d52f288
BLAKE2b-256 e4c831514eee255feee93f664d2ebc9957b69db4a0bdf89d2c06c63caa79b836

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for oxlsx-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 2ca750ab9e1626df13a6bf67a5a12131ef6a8b53bdbbe4af7f3fc7459cf37287
MD5 bedcf0ad982416ab1e054c24d6f7cc64
BLAKE2b-256 49de726b3f9e3c783d72733af65808dd34c8ccc262582dc09608453e22377d08

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page