Skip to content

Python Integration

The Python package is intended for scripts, notebooks, tests, and data pipelines that need Excel-compatible recalculation and workbook editing without launching Excel.

Best fit

Use Python when the spreadsheet is part of a batch job or analysis workflow. Use WASM when the workbook should stay in the browser.

Glossary: wasmtime

A standalone WebAssembly runtime maintained by the Bytecode Alliance. The Formulon Python package ships a portable formulon_capi.wasm and uses wasmtime to load it at import time — so the wheel is py3-none-any and works wherever a wasmtime wheel is available.

Glossary: C ABI

A flat C function interface exposed by the Formulon native library. Bindings call into it through a stable contract; the Python wrapper, the CLI, and the WASM build all sit on top of the same C ABI. See C ABI.

Typical use cases:

  • validating formulas in uploaded workbooks,
  • recalculating reports in batch jobs,
  • comparing workbook output against checked-in goldens,
  • extracting calculated values for downstream systems,
  • editing workbook structure, styles, comments, validations, conditional formats, and PivotTables from Python.

Keep workbook IO at the edges of your script and make the selected profile explicit in test fixtures.

Packaging

The PyPI package does not ship a platform-native libformulon. It ships one py3-none-any wheel with formulon_capi.wasm and a pure-Python wrapper; wasmtime supplies the platform-specific runtime that loads the module. There is no Cython, pybind11, or NumPy dependency at runtime.

API scope

Workbook mirrors the C ABI surface exposed by the npm bindings. In addition to load -> mutate -> recalc -> save, the wrapper exposes sheet and matrix edits, defined names, partial recalc, merges, comments, hyperlinks, validations, styles, conditional formatting, PivotTables, dependency tracing, spill inspection, function metadata, sheet view/protection, calc policy, and external links.

The main runtime difference is threading: Python drives the C ABI through wasmtime, so recalc() is serial under WASM. Result fidelity is the same as other surfaces.

Python does not yet expose the v0.9.4 additions

evaluateFormulaText, evaluateConditionalFormula, and comment enumeration (getComments on the Node addon; fm_sheet_get_comment_count / fm_sheet_get_comment_at_index on the C API) ship in v0.9.4 for the C API, Node addon, and WASM only. The Python wrapper has no scalar equivalent yet — "mirrors the C ABI surface" describes the general shape of the binding, not full one-to-one method parity. Python's add_conditional_format already returns the new rule's index, the same v0.9.4 C-API behavior the Node addon exposes.

As of v0.9.5, Python does expose the whole-array variant evaluate_formula_array(sheet, row, col, formula) — a read-only, non-mutating evaluation that returns the entire dynamic-array result rather than reducing to the top-left element — and the pure merge_function_metadata(base, entry, locale) helper that layers host-supplied localized function metadata over the engine's structural catalog. The scalar evaluate_formula_text / evaluate_conditional_formula are still Node addon / WASM / C-API only.

Error handling

FormulonError means the host operation failed: invalid bytes, a bad handle, IO trouble, or an internal engine failure. Excel cell errors are returned as Value(kind=ValueKind.ERROR).

python
import formulon
from formulon import ValueKind, FormulonError

try:
    value = formulon.eval_formula("=1/0")
    assert value.kind is ValueKind.ERROR  # cell error
except FormulonError as e:
    # host error: not a cell error
    raise

Lifetime

Use Workbook as a context manager. The wrapper releases the native handle on exit, even if an exception is raised inside the block:

python
from formulon import Workbook

with Workbook.create_default() as wb:
    wb.set_formula(0, 0, 0, "=SUM(1,2,3)")
    wb.recalc()
    print(wb.get_value(0, 0, 0).to_python())

Batch recalculation pattern

python
from formulon import Workbook

with open("input.xlsx", "rb") as f:
    blob = f.read()

with Workbook.load(blob) as wb:
    wb.set_number(0, 3, 1, 125_000.0)
    wb.recalc()
    output = wb.save()

with open("output.xlsx", "wb") as f:
    f.write(output)

This is the canonical pattern: load → mutate → recalc → save. See Python batch recalculation for a fuller example.