The fastest openpyxl-compatible Excel library for Python.
Drop-in replacement backed by Rust — up to 5x faster with zero code changes.
- from openpyxl import load_workbook, Workbook
- from openpyxl.styles import Font, PatternFill, Alignment, Border
+ from wolfxl import load_workbook, Workbook, Font, PatternFill, Alignment, BorderYour existing code works as-is. Same ws["A1"].value, same Font(bold=True), same wb.save().
Measured with ExcelBench on Apple M1 Pro, Python 3.12, median of 3 runs.
pip install wolfxlfrom wolfxl import load_workbook, Workbook, Font, PatternFill
# Write a styled spreadsheet
wb = Workbook()
ws = wb.active
ws["A1"].value = "Product"
ws["A1"].font = Font(bold=True, color="FFFFFF")
ws["A1"].fill = PatternFill(fill_type="solid", fgColor="336699")
ws["A2"].value = "Widget"
ws["B2"].value = 9.99
wb.save("report.xlsx")
# Read it back — styles included
wb = load_workbook("report.xlsx")
ws = wb[wb.sheetnames[0]]
for row in ws.iter_rows(values_only=False):
for cell in row:
print(cell.coordinate, cell.value, cell.font.bold)
wb.close()
| Mode | Usage | Engine | What it does |
|---|---|---|---|
| Read | load_workbook(path) |
calamine-styles | Parse XLSX with full style extraction |
| Write | Workbook() |
rust_xlsxwriter | Create new XLSX files from scratch |
| Modify | load_workbook(path, modify=True) |
XlsxPatcher | Surgical ZIP patch — only changed cells are rewritten |
Modify mode preserves everything it doesn't touch: charts, macros, images, pivot tables, VBA.
| Category | Features |
|---|---|
| Data | Cell values (string, number, date, bool), formulas, hyperlinks, comments |
| Styling | Font (bold, italic, underline, color, size), fills, borders, number formats, alignment |
| Structure | Multiple sheets, merged cells, named ranges, freeze panes, tables |
| Advanced | Data validation, conditional formatting |
| Scale | File size | WolfXL Read | openpyxl Read | WolfXL Write | openpyxl Write |
|---|---|---|---|---|---|
| 100K cells | 400 KB | 0.11s | 0.42s | 0.06s | 0.28s |
| 1M cells | 3 MB | 1.1s | 4.0s | 0.9s | 2.9s |
| 5M cells | 25 MB | 6.0s | 20.9s | 3.2s | 15.5s |
| 10M cells | 45 MB | 13.0s | 47.8s | 6.7s | 31.8s |
Throughput stays flat as files grow — no hidden O(n^2) pathology.
Every Rust-backed Python Excel project picks a different slice of the problem. WolfXL is the only one that covers all three: formatting, modify mode, and openpyxl API compatibility.
| Library | Read | Write | Modify | Styling | openpyxl API |
|---|---|---|---|---|---|
| fastexcel | Yes | — | — | — | — |
| python-calamine | Yes | — | — | — | — |
| FastXLSX | Yes | Yes | — | — | — |
| rustpy-xlsxwriter | — | Yes | — | Partial | — |
| WolfXL | Yes | Yes | Yes | Yes | Yes |
- Styling = reads and writes fonts, fills, borders, alignment, number formats
- Modify = open an existing file, change cells, save back — without rebuilding from scratch
- openpyxl API = same
load_workbook,Workbook,Cell,Font,PatternFillobjects
Upstream calamine does not parse styles. WolfXL's read engine uses calamine-styles, a fork that adds Font/Fill/Border/Alignment/NumberFormat extraction from OOXML.
For write-heavy workloads, use append() or write_rows() instead of cell-by-cell access. These APIs buffer rows as raw Python lists and flush them to Rust in a single call at save time, bypassing per-cell FFI overhead entirely.
from wolfxl import Workbook
wb = Workbook()
ws = wb.active
# append() — fast sequential writes (3.7x faster than cell-by-cell)
ws.append(["Name", "Amount", "Date"])
for row in data:
ws.append(row)
# write_rows() — fast writes at arbitrary positions
ws.write_rows(header_grid, start_row=1, start_col=1)
ws.write_rows(data_grid, start_row=5, start_col=1)
wb.save("output.xlsx")For reads, iter_rows(values_only=True) uses a fast bulk path that reads all values in a single Rust call (6.7x faster than openpyxl):
wb = load_workbook("data.xlsx")
ws = wb[wb.sheetnames[0]]
for row in ws.iter_rows(values_only=True):
process(row) # row is a tuple of plain Python values| API | vs openpyxl | How |
|---|---|---|
ws.append(row) |
3.7x faster write | Buffers rows, single Rust call at save |
ws.write_rows(grid) |
3.7x faster write | Same mechanism, arbitrary start position |
ws.iter_rows(values_only=True) |
6.7x faster read | Single Rust call, no Cell objects |
ws.cell(r, c, value=v) |
1.6x faster write | Per-cell FFI (compatible but slower) |
WolfXL includes a built-in formula evaluator with 62 functions across 7 categories. Calculate formulas without external dependencies - no need for formulas or xlcalc.
from wolfxl import Workbook
from wolfxl.calc import calculate
wb = Workbook()
ws = wb.active
ws["A1"] = 100
ws["A2"] = 200
ws["A3"] = "=SUM(A1:A2)"
ws["B1"] = "=PMT(0.05/12, 360, -300000)" # monthly mortgage payment
results = calculate(wb)
print(results["Sheet!A3"]) # 300
print(results["Sheet!B1"]) # 1610.46...
# Recalculate after changes
ws["A1"] = 500
results = calculate(wb)
print(results["Sheet!A3"]) # 700| Category | Functions |
|---|---|
| Math (10) | SUM, ABS, ROUND, ROUNDUP, ROUNDDOWN, INT, MOD, POWER, SQRT, SIGN |
| Logic (5) | IF, AND, OR, NOT, IFERROR |
| Lookup (7) | VLOOKUP, HLOOKUP, INDEX, MATCH, OFFSET, CHOOSE, XLOOKUP |
| Statistical (13) | AVERAGE, AVERAGEIF, AVERAGEIFS, COUNT, COUNTA, COUNTIF, COUNTIFS, MIN, MINIFS, MAX, MAXIFS, SUMIF, SUMIFS |
| Financial (7) | PV, FV, PMT, NPV, IRR, SLN, DB |
| Text (13) | LEFT, RIGHT, MID, LEN, CONCATENATE, UPPER, LOWER, TRIM, SUBSTITUTE, TEXT, REPT, EXACT, FIND |
| Date (8) | TODAY, DATE, YEAR, MONTH, DAY, EDATE, EOMONTH, DAYS |
Named ranges are resolved automatically. Error values (#N/A, #VALUE!, #DIV/0!, #REF!, #NUM!, #NAME?) propagate through formula chains like real Excel. Install pip install wolfxl[calc] for extended formula coverage via the formulas library fallback.
SynthGL switched from openpyxl to WolfXL for their GL journal exports (14-column financial data, 1K-50K rows). Results: 4x faster writes, 9x faster reads at scale. 50K-row exports dropped from 7.6s to 1.3s. Read the full case study.
WolfXL is a thin Python layer over compiled Rust engines, connected via PyO3. The Python side uses lazy cell proxies — opening a 10M-cell file is instant. Values and styles are fetched from Rust only when you access them. On save, dirty cells are flushed in one batch, avoiding per-cell FFI overhead.
MIT