BetterOffice

Python

betteroffice-docx, betteroffice-xlsx, and betteroffice-pptx on PyPI — read, edit, lay out, render, and save

pip install betteroffice-docx
pip install betteroffice-xlsx
pip install betteroffice-pptx

All three formats are on PyPI. The Rust engine is compiled into each wheel: no Word, no Excel, no PowerPoint, no LibreOffice subprocess, no COM. Wheels are built for Linux (x86_64, aarch64), macOS (arm64, x86_64), and Windows (x86_64) against the stable ABI for CPython 3.9 and up.

betteroffice-xlsx is the workbook distribution, and the rest of this page walks through it; DOCX and PPTX follow at the end.

Formulas calculate

from betteroffice_xlsx import Workbook

wb = Workbook.open_path("budget.xlsx")
sheet = wb["Sheet1"]

sheet["B1"] = 10
sheet["B2"] = 32
sheet["B3"] = "=SUM(B1:B2)"

print(sheet["B3"])           # 42.0, computed here
print(sheet.formula("B3"))   # 'SUM(B1:B2)'

wb.save_path("budget-out.xlsx")

Writing a cell recalculates its dependents. A cell you have not touched keeps whatever the authoring application cached, stale or not; Workbook.open_recalculated or a later recalculate() evaluates the whole workbook with this engine instead. Values come back as None, float, str, bool, or a CellError that compares equal to its code, so #DIV/0! as a value stays distinguishable from a cell holding that text.

Render a sheet

png = wb.render_png("Sheet1", scale=2.0, range="A1:H40")
png.write("preview.png")

This is the same grid layout and display list the browser editor uses, and the raster backend embeds Carlito to measure and draw cell text, so nothing has to be registered first. Opening, recalculating, rendering, and saving release the GIL, so they run in parallel across threads instead of serializing your workers.

What a save keeps

save() returns bytes and save_path() writes a file. The parts the model does not represent — charts, pivot tables, comments, macros, custom XML — are kept, and sheets an edit did not touch are copied through byte for byte. A sheet you do edit is reserialized from the model, so unmodeled markup on that one sheet is lost.

Collaboration and agent proposals

Workbook.open_collaborative makes the workbook a Yrs replica and exposes the byte-level primitives — state_vector, state_as_update, diff, apply_update — rather than a transport, so it drops into a WebSocket server, a queue, or a test harness without committing you to asyncio. propose stages an agent's edits with the before and after text of each one, and nothing reaches the sheet until accept_proposal applies it as a single undo step.

The package README on PyPI carries the full API table, the openpyxl comparison, and the error hierarchy.

DOCX

betteroffice-docx reads paragraphs, tables, and sections, rewrites the text of a plain single-run paragraph, and saves back to DOCX with everything the model does not represent reused from the package it opened. It paginates and rasterizes too: the layout pass takes an already-measured projection of the document, because the Rust crates do not lower the DOCX model into measured blocks yet, and render_png draws one page of the resulting display list. No font is compiled into that wheel, and a page whose text names a family you never registered is an error rather than a page of invisible text.

PPTX

betteroffice-pptx reads decks, edits shapes and text, saves the edits back to .pptx bytes, and lays slides out into the same display-list contract the canvas paints. render_slide fails until register_font has supplied at least one face — no font is compiled into that wheel. A Presentation is also pinned to the thread that opened it, and must be released there.

On this page