Openpyxl-compatible Excel automation with a Rust backend.
Read, write, and surgically modify workbooks, including charts, images, encryption, structural ops, and pivot-table construction.
- from openpyxl import load_workbook, Workbook
- from openpyxl.styles import Font, PatternFill, Alignment, Border
+ from wolfxl import load_workbook, Workbook, Font, PatternFill, Alignment, BorderMost workbook automation keeps the same shape: ws["A1"].value,
Font(bold=True), and wb.save() all work the way openpyxl users
expect.
Fresh WolfXL 2.0 release-artifact evidence is available in ExcelBench: wheel-backed rerun, 18/18 green features, and dated performance snapshots.
- WolfXL package surface is at
2.0.0. - The next release is gated on a fresh verification pass: Rust workspace tests, Python package tests, parity slices, clean install/import smoke, and ExcelBench refresh.
- Historical wheel-backed ExcelBench evidence is available in the 2026-04-28 release snapshot fidelity report.
- The paired dated evidence includes the release snapshot dashboard and the matching perf snapshot; refresh these before using them as next-release proof.
- Use the Public Evidence Status page to see which claims are current, historical, or still gated.
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()import wolfxl
from wolfxl.chart import Reference
from wolfxl.pivot import PivotCache, PivotTable
wb = wolfxl.load_workbook("source-data.xlsx", modify=True)
ws = wb.active
src = Reference(ws, min_col=1, min_row=1, max_col=4, max_row=100)
cache = wb.add_pivot_cache(PivotCache(source=src))
pt = PivotTable(
cache=cache, location="F2",
rows=["region"], cols=["quarter"], data=[("revenue", "sum")],
)
ws.add_pivot_table(pt)
wb.save("pivot.xlsx")WolfXL constructs pivot tables with pre-aggregated records —
pivots open in Excel, LibreOffice, and openpyxl with data populated,
through the modify-mode patcher (load_workbook(..., modify=True)),
with no refresh-on-open required. In the project comparison below, openpyxl
preserves pivots on round-trip but does not provide this constructor,
and XlsxWriter does not support pivots.
Link a chart to the pivot:
from wolfxl.chart import BarChart
chart = BarChart()
chart.pivot_source = pt # emits <c:pivotSource> + per-series <c:fmtId>
ws.add_chart(chart, "F18")Existing pivots can also be edited in modify mode: source ranges,
row/column/page field placement, page-field selection, and data-field
aggregation all route through PivotTableHandle. Layout edits stamp
refreshOnLoad="1" so Excel refreshes derived pivot cache records on
open; source-range edits with the same shape stay byte-stable.
| Mode | Usage | Engine | What it does |
|---|---|---|---|
| Read | load_workbook(path) |
NativeXlsxBook | Parse XLSX with values, formulas, styles, metadata, drawings, and tables |
| Write | Workbook() |
rust_xlsxwriter | Create new XLSX files from scratch |
| Streaming write | Workbook(write_only=True) |
Per-sheet temp file + <sheetData> splice |
Append-only, bounded-memory ETL exports — peak RSS dominated by SST + styles, not by row data |
| 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.
Features marked Preserved are kept verbatim on modify-mode round-trip (open, edit other cells, save). Construction from Python code for those features is tracked below as roadmap items.
| Category | Features |
|---|---|
| Data | Cell values (string, number, date, bool), formulas, comments, hyperlinks |
| Styling | Font (bold, italic, underline, color, size), fills, borders, number formats, alignment; Color(theme=...) and Color(indexed=...) accepted |
| Structure | Multiple sheets, merged cells, defined names (read + write), freeze panes, row heights, column widths, document properties |
| Tables / Validation / CF | ws.tables, ws.add_table, ws.data_validations, ws.conditional_formatting (read + write in Workbook() mode) |
| Charts | 16 chart families — BarChart, LineChart, PieChart, DoughnutChart, AreaChart, ScatterChart, BubbleChart, RadarChart, BarChart3D, LineChart3D, PieChart3D, AreaChart3D, SurfaceChart, SurfaceChart3D, StockChart, ProjectedPieChart |
| Pivots | PivotCache, PivotTable, RowField / ColumnField / DataField / PageField; slicers, calculated fields/items, GroupItems; pivot-chart linkage via chart.pivot_source = pt; deep-clone of pivot-bearing sheets |
| Images | Image (PNG / JPEG / GIF / BMP); one-cell, two-cell, absolute anchors; modify-mode add_image |
| Encryption | Read + write Agile (AES-256 / SHA-512) via wolfxl[encrypted] |
| Iteration | iter_rows, iter_cols, rows, columns, values, range slicing (ws["A1:B2"], ws["A:B"], ws[1:3]) |
| Utils | get_column_letter, column_index_from_string, coordinate_to_tuple, range_boundaries, absolute_coordinate, quote_sheetname, range_to_tuple, rows_from_range, cols_from_range, get_column_interval, dataframe_to_rows, is_date_format |
| Preserved / linked content | Macros (VBA) — round-trip cleanly through modify mode with raw xl/vbaProject.bin bytes available via Workbook.vba_archive for inspection (no authoring API); external workbook links (wb._external_links exposes target / sheet_names / cached values and supports append/remove/update-target authoring); embedded objects also round-trip |
Modules that import from openpyxl generally work against wolfxl. Unsupported classes raise NotImplementedError with a clear hint at the construction site - no silent no-ops.
| Class / API | Status |
|---|---|
Font, PatternFill, Border, Side, Alignment, Color |
Full support |
Comment, Hyperlink |
Read + write (write mode); modify-mode setters T1.5 |
DataValidation, Table, TableStyleInfo, TableColumn |
Read + write (write mode); modify-mode setters T1.5 |
CellIsRule, FormulaRule, ColorScaleRule, DataBarRule, IconSetRule |
Read + write (write mode); modify-mode setters T1.5 |
DefinedName, DocumentProperties |
Read + write (write mode); modify-mode setters T1.5 |
NamedStyle, Protection, GradientFill, DifferentialStyle |
Constructor / dataclass support |
BarChart, LineChart, PieChart, Reference, Series (from wolfxl.chart) |
Full support (1.6+) — 16 chart families incl. 3D / Stock / Surface / ProjectedPie |
Image (from wolfxl.drawing.image) |
Full support (1.5+) — PNG / JPEG / GIF / BMP, all anchor types |
PivotTable, PivotCache (from wolfxl.pivot) |
Full support (2.0+) — modify-mode construction + chart linkage + deep-clone |
AutoFilter |
Read + write support |
ws.insert_rows, ws.delete_rows |
Full support (modify mode, 1.1+) — RFC-030 |
ws.insert_cols, ws.delete_cols |
Full support (modify mode, 1.1+) — RFC-031 |
ws.move_range |
Full support (modify mode, 1.1+) — RFC-034 |
wb.move_sheet |
Full support (modify mode, 1.1+) — RFC-036 |
wb.copy_worksheet |
Full support (write + modify mode, 1.1+) — RFC-035 |
The v2.0 benchmark table is intentionally withheld until the current
audit refreshes the ExcelBench run, pivot-construction microbenchmark,
and clean install/import smoke from the release artifact. Do not use
draft 10×-100× marketing copy or placeholder benchmark rows as
release evidence.
For historical context and reproducible benchmark commands, see Benchmark Results. For the current claim status, see Public Evidence Status.
The implementation goal remains stable throughput as files grow, with linear pivot construction over source-row count.
Every Rust-backed Python Excel project picks a different slice of the problem. WolfXL is the only one in this comparison that covers all four: formatting, modify mode, openpyxl API compatibility, and pivot-table construction.
| Library | Read | Write | Modify | Styling | openpyxl API | Pivots |
|---|---|---|---|---|---|---|
| fastexcel | Yes | — | — | — | — | — |
| python-calamine | Yes | — | — | — | — | — |
| FastXLSX | Yes | Yes | — | — | — | — |
| rustpy-xlsxwriter | — | Yes | — | Partial | — | — |
| openpyxl | Yes | Yes | Yes (full DOM) | Yes | Native | Round-trip only* |
| XlsxWriter | — | Yes | — | Yes | — | — |
| WolfXL | Yes | Yes | Yes (surgical) | Yes | Yes | Yes (construction + linkage) |
- 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 - Pivots = construct a pivot table from Python with pre-aggregated records (the file opens in Excel / LibreOffice / openpyxl with data populated, no refresh-on-open)
*openpyxl preserves pivot tables on round-trip but does not provide a
Python-side constructor that emits the pivotCacheRecords snapshot.
WolfXL's public ecosystem claim here is pending the final
public-launch truth pass.
WolfXL's public .xlsx and .xlsb readers are native. .xlsb is read-only but exposes values, cached formula results, and cell styles; legacy .xls remains on the Calamine-backed value path.
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. Historical ExcelBench numbers
are shown below; the v2.0 release audit is refreshing them before any
new public speedup headline is made.
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 valuesFor ingestion, dataframe, or review workflows that need values plus formatting
signals, use cell_records(). It returns compact dictionaries without creating
one Python Cell object per coordinate:
records = ws.cell_records(
include_format=True,
include_formula_blanks=False,
include_coordinate=False,
)
for record in records:
print(record["row"], record["column"], record["value"], record.get("number_format"))| API | Last audited 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_records() |
Fast styled sparse read | Single Rust call, values plus compact format metadata |
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.
For a representative finance workflow, see Template-Driven Finance Workbook Updates. It benchmarks the exact workload where WolfXL is most differentiated: open an existing workbook with formulas, charts, comments, validations, and large detail sheets, touch three assumption cells, and save.
For a fresh-workbook export path, see
Styled Report Generation.
The current local snapshot on a 10k-row reporting workbook came in at
0.553s for openpyxl vs 0.380s for WolfXL, about 1.46x faster.
For the "update one data block but keep the workbook" workflow, see
Workbook-Preserving ETL Update.
The current local snapshot for replacing a 2,000-row block inside a
20,000-row reporting workbook came in at 1.064s for openpyxl vs
0.496s for WolfXL, about 2.15x faster.
For a capability-first example, see
Pivot Construction From Python.
It demonstrates workbook-scoped pivot cache creation, pivot-table emit,
and pivot-linked chart output through WolfXL modify mode.
The current local snapshot constructed the full artifact on a 10,000-row
source sheet in 0.229s median with emitted pivot cache, pivot records,
pivot table, and chart <c:pivotSource> parts all validated.
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