Skip to main content

Formualizer for Python

Arrow Powered PyPI License: MIT/Apache-2.0 Documentation

Formualizer banner


Parse, evaluate, and mutate Excel workbooks at native speed from Python.

A Rust-powered spreadsheet engine with 320+ Excel-compatible functions, exposed through a clean Pythonic API. Tokenize formulas, walk ASTs, evaluate workbooks, and use SheetPort to treat spreadsheets as typed APIs.

Installation

pip install formualizer

Prebuilt wheels are available for Python 3.10-3.13 on Linux, macOS, and Windows. No Rust toolchain required.

Documentation

Full documentation at formualizer.dev:

Quick start

Evaluate a workbook

import formualizer as fz

wb = fz.Workbook()
s = wb.sheet("Sheet1")

s.set_value(1, 1, fz.LiteralValue.number(1000.0))  # A1: principal
s.set_value(2, 1, fz.LiteralValue.number(0.05))  # A2: annual rate
s.set_value(3, 1, fz.LiteralValue.number(12.0))  # A3: periods

s.set_formula(1, 2, "=PMT(A2/12, A3, -A1)")
print(wb.evaluate_cell("Sheet1", 1, 2))  # ~85.61

Load an XLSX and evaluate

import formualizer as fz

wb = fz.load_workbook("financial_model.xlsx", strategy="eager_all")
print(wb.evaluate_cell("Summary", 1, 2))

# Optional native read-only mapping. The underlying file must not be
# destructively modified or truncated while it is loading.
mapped = fz.load_workbook(
    "financial_model.xlsx",
    path_source=fz.XlsxPathSource.DIRECT_MMAP,
)

Load and save XLSX bytes

import formualizer as fz

payload = open("financial_model.xlsx", "rb").read()
wb = fz.load_workbook_bytes(payload)
print(wb.evaluate_cell("Summary", 1, 2))

out = wb.to_xlsx_bytes()

Native Python builds use calamine by default for both path-based and byte-oriented XLSX loading. Pyodide currently defaults to umya, which also remains available explicitly on native builds. XLSX byte export uses umya because Calamine is read-only.

Recalculate XLSX cached values (writeback)

import formualizer as fz

# in-place
summary = fz.recalculate_file("financial_model.xlsx")
print(summary["status"], summary["evaluated"], summary["errors"])

# write to a new file
summary = fz.recalculate_file(
    "financial_model.xlsx", output="financial_model.recalc.xlsx"
)

Formula text is preserved. Cached-value typing follows the active umya-spreadsheet implementation.

Cache-only XLSX recalculation

result = fz.recalculate_xlsx_bytes(payload)
assert isinstance(result["bytes"], bytes)
print(result["summary"]["status"], result["cache_cells_changed"])

# The file API snapshots input and atomically replaces the destination on success.
result = fz.recalculate_xlsx_file("model.xlsx", output="model.recalc.xlsx")

These APIs use the shared cache-only Rust implementation, retaining formula text and unrelated package members. Safe core resource limits apply; error_location_limit= only caps retained error locations.

Parse and analyze formulas

from formualizer import parse
from formualizer.visitor import collect_references, collect_function_names

ast = parse("=SUMIFS(Revenue,Region,A1,Year,B1)")
print(ast.pretty())  # indented AST tree
print(ast.to_formula())  # canonical Excel string
print(collect_references(ast))  # [Revenue, Region, A1, Year, B1]
print(collect_function_names(ast))  # ['SUMIFS']

Key features

Capability Description
Tokenization Break formulas into structured Token objects with byte spans and operator metadata
Parsing Produce a rich AST with reference normalization, source tracking, and 64-bit structural fingerprints
320+ built-in functions Math, text, lookup (XLOOKUP, VLOOKUP), date/time, financial, statistics, database, engineering
Workbook evaluation Set values and formulas, evaluate cells/ranges, load XLSX/CSV/JSON
XLSX cache writeback recalculate_file(path, output=None) recalculates formulas and writes cached values back
Batch operations set_values_batch / set_formulas_batch for efficient bulk updates
Undo / redo Optional changelog with automatic action grouping — single edits are individually undoable
Evaluation planning Inspect the dependency graph and evaluation schedule before computing
SheetPort Treat spreadsheets as typed functions with YAML manifests, schema validation, and batch scenarios
Deterministic mode Inject clock, timezone, and RNG seed for reproducible evaluation
Visitor utilities walk_ast, collect_references, collect_function_names for ergonomic tree traversal
Rich errors Typed TokenizerError / ParserError / ExcelEvaluationError with position info

Workbook evaluation

import formualizer as fz

wb = fz.Workbook()
s = wb.sheet("Data")

# Set values and formulas
s.set_value(1, 1, fz.LiteralValue.number(100.0))
s.set_value(2, 1, fz.LiteralValue.number(200.0))
s.set_value(3, 1, fz.LiteralValue.number(300.0))
s.set_formula(4, 1, "=SUM(A1:A3)")
s.set_formula(4, 2, "=AVERAGE(A1:A3)")

print(wb.evaluate_cell("Data", 4, 1))  # 600.0
print(wb.evaluate_cell("Data", 4, 2))  # 200.0

Custom functions

Register workbook-local callbacks without forking Formualizer:

import formualizer as fz

wb = fz.Workbook(mode=fz.WorkbookMode.Ephemeral)
wb.add_sheet("Sheet1")

wb.register_function(
    "py_add",
    lambda a, b: a + b,
    min_args=2,
    max_args=2,
)

wb.set_formula("Sheet1", 1, 1, "=PY_ADD(20,22)")
print(wb.evaluate_cell("Sheet1", 1, 1))  # 42
print(wb.list_functions())
wb.unregister_function("py_add")

Key semantics:

  • Names are case-insensitive and stored canonically (py_add -> PY_ADD).
  • Custom functions are workbook-local and take precedence over global built-ins.
  • Built-in override is disabled by default; set allow_override_builtin=True to opt in.
  • Args are passed by value; range inputs arrive as nested Python lists.
  • Return Python primitives, datetime/date/time/timedelta, dict error objects, or nested lists for array spill output.
  • Python callback exceptions are sanitized and mapped to #VALUE!.

Runnable example: python bindings/python/examples/custom_function_registration.py

Batch operations

# Bulk-set values (auto-grouped as one undo step when changelog is enabled)
s.set_values_batch(
    1,
    1,
    3,
    2,
    [
        [fz.LiteralValue.number(10.0), fz.LiteralValue.number(20.0)],
        [fz.LiteralValue.number(30.0), fz.LiteralValue.number(40.0)],
        [fz.LiteralValue.number(50.0), fz.LiteralValue.number(60.0)],
    ],
)

Undo / redo

The changelog is opt-in. Once enabled, every edit is tracked:

wb.set_changelog_enabled(True)

s.set_value(1, 1, fz.LiteralValue.number(10.0))
s.set_value(1, 1, fz.LiteralValue.number(20.0))
wb.undo()  # back to 10
wb.redo()  # back to 20

# Batch methods are auto-grouped as one undo step.
# For manual grouping of multiple calls:
wb.begin_action("update prices")
s.set_value(1, 1, fz.LiteralValue.number(100.0))
s.set_value(2, 1, fz.LiteralValue.number(200.0))
wb.end_action()
wb.undo()  # reverts both values at once

Evaluation planning

Inspect what the engine will compute before running:

plan = wb.get_eval_plan([("Sheet1", 1, 2)])
print(f"Vertices to evaluate: {plan.total_vertices_to_evaluate}")
print(f"Parallel layers: {plan.estimated_parallel_layers}")
for layer in plan.layers:
    print(f"  Layer: {layer.vertex_count} vertices, parallel={layer.parallel_eligible}")

# By default this will build deferred workbook graphs if needed.
# Disable that behavior if you want planning to fail instead of mutating workbook state.
wb.get_eval_plan([("Sheet1", 1, 2)], build_graph_if_needed=False)

SheetPort: spreadsheets as typed APIs

Define a YAML manifest to treat a spreadsheet as a typed function with validated inputs/outputs:

from formualizer import SheetPortSession, Workbook

manifest_yaml = """
spec: fio
spec_version: "0.3.0"
manifest:
  id: pricing-model
  name: Pricing Model
  workbook:
    uri: memory://pricing.xlsx
    locale: en-US
    date_system: 1900
ports:
  - id: base_price
    dir: in
    shape: scalar
    location: { a1: Inputs!A1 }
    schema: { type: number }
  - id: final_price
    dir: out
    shape: scalar
    location: { a1: Outputs!A1 }
    schema: { type: number }
"""

wb = Workbook()
wb.add_sheet("Inputs")
wb.add_sheet("Outputs")
wb.set_formula("Outputs", 1, 1, "=Inputs!A1*1.2")

session = SheetPortSession.from_manifest_yaml(manifest_yaml, wb)
session.write_inputs({"base_price": 100.0})
result = session.evaluate_once(freeze_volatile=True)
print(result["final_price"])  # 120.0

API reference

Top-level functions

tokenize(formula: str, dialect: FormulaDialect = None) -> Tokenizer
parse(formula: str, dialect: FormulaDialect = None) -> ASTNode
load_workbook(path: str, strategy: str = None, *, path_source: XlsxPathSource | None = None) -> Workbook
load_workbook_bytes(data: bytes, strategy: str = None, backend: str | None = None) -> Workbook
recalculate_file(path: str, output: str | None = None) -> dict

Core classes

  • Workbook — create, load, evaluate, undo/redo. Supports from_path(), from_bytes(), load_path(), and to_xlsx_bytes().
  • Sheet — per-sheet facade for set_value, set_formula, get_cell, batch operations.
  • LiteralValue — typed values: .int(), .number(), .text(), .boolean(), .date(), .empty(), .error(), .array().
  • Tokenizer — iterable token sequence with .render() and .tokens.
  • ASTNode — .pretty(), .to_formula(), .fingerprint(), .children(), .walk_refs().
  • CellRef / RangeRef / TableRef / NamedRangeRef — typed references.
  • SheetPortSession — bind manifests to workbooks, read/write typed ports, evaluate.
  • EvaluationConfig — tune parallel evaluation, warmup, range limits, date systems.

Visitor helpers (formualizer.visitor)

walk_ast(node, visitor_fn)  # DFS with VisitControl (CONTINUE/SKIP/STOP)
collect_references(node)  # -> list[ReferenceLike]
collect_function_names(node)  # -> list[str]
collect_nodes_by_type(node, "Function")  # -> list[ASTNode]

Full type stubs are included in the package (.pyi files) for IDE autocompletion and mypy.


Building from source

Requires Rust >= 1.70 and maturin:

pip install maturin
cd bindings/python
maturin develop            # debug build
maturin develop --release  # optimized build

Using in Pyodide (browser / WebAssembly)

Native wheels are published to PyPI. Pyodide wheels are built and smoke-tested in CI and release workflows, then uploaded only as the wheels-pyodide Actions artifact; they are not uploaded to PyPI or attached to GitHub Releases. Download and extract the artifact (or build locally), then host the compatible wheel at a browser-accessible URL with suitable CORS headers. An Actions artifact ZIP is not a wheel URL:

import micropip

wheel_url = "<your-downloadable-wheel-url>"
await micropip.install(wheel_url)

import formualizer as fz

wb = fz.Workbook()
wb.add_sheet("Sheet1")
wb.set_value("Sheet1", 1, 1, 20)
wb.set_value("Sheet1", 2, 1, 22)
wb.set_formula("Sheet1", 1, 2, "=SUM(A1:A2)")
wb.evaluate_cell("Sheet1", 1, 2)  # -> 42.0

Tested Pyodide target: CI and release smoke tests use Pyodide 0.29.3 and the wheel's derived ABI (currently pyodide_2025_0). Rebuild and smoke-test a wheel when targeting another runtime; no persistent public wheel URL is promised.

Pyodide-specific behavior:

  • EvaluationConfig() and Workbook() default enable_parallel = False on sys.platform == "emscripten" (Pyodide has no threads). You can still opt in, but it falls back to single-threaded execution.
  • Native XLSX byte loading (Workbook.from_bytes, load_workbook_bytes) defaults to calamine; Pyodide defaults to umya. XLSX byte export uses umya on all platforms.
  • Python UDFs registered via Workbook.register_function work identically to native; single-cell refs arrive as scalars (Excel-native semantics).

Building a Pyodide wheel from source

For local development or targeting a Pyodide version without a retained Actions artifact:

./scripts/build-pyodide-wheel.sh
./scripts/smoke-pyodide-wheel.sh dist/pyodide/*-pyodide_*_wasm32.whl

The build script defaults to xbuildenv Pyodide 0.29.3, derives Python, ABI, Emscripten, and Rust toolchain values from pyodide config, installs Pyodide's custom wasm-EH Rust sysroot over the stock rustup target, and retags the output wheel to the platform tag Pyodide's micropip expects. pyodide-cli and pyodide-build are resolved through uvx and are not pinned by the script.

Testing

pip install formualizer[dev]
pytest bindings/python/tests
ruff check bindings/python
mypy bindings/python/formualizer

Workspace layout

formualizer/
  crates/                    # Rust core (parse, eval, workbook, sheetport)
  bindings/python/
    formualizer/             # Python package (helpers, visitor, type stubs)
    src/                     # PyO3 bridge (Rust -> Python)

The Python wheel links directly against the Rust crates — there is no runtime FFI overhead beyond the initial C-to-Rust boundary.

License

Dual-licensed under MIT or Apache-2.0, at your option.

Metadata

Release files for formualizer 0.9.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for formualizer 0.9.2
File Size Uploaded
formualizer-0.9.2.tar.gz 2.3 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for formualizer 0.9.2
File
formualizer-0.9.2-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
formualizer-0.9.2-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
formualizer-0.9.2-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
formualizer-0.9.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
formualizer-0.9.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
formualizer-0.9.2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
formualizer-0.9.2-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 59.2 MB

Release files / formualizer-0.9.2.tar.gz

Download URL formualizer-0.9.2.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
43fe1ce899d0361d5a90f1a7ebc8cf70187dca4b756d18da1ca49f83caeb6101
BLAKE2b-256 checksum
How to use checksums
0694a96ec5f9da6ba8a4cd6d909322a38a61abe625672dd0e4aba5fc69a5df90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-win_amd64.whl

Download URL formualizer-0.9.2-cp310-abi3-win_amd64.whl
Size 7.9 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
1f9615a10588af3317cf42539aa7f0b574f864418e2ff691397236162997db3b
BLAKE2b-256 checksum
How to use checksums
756e044cc6014c5000ea22f6f2d6a1c22608ebf2974d5bb0911c9ef9a0364b1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL formualizer-0.9.2-cp310-abi3-musllinux_1_2_x86_64.whl
Size 8.6 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
7d9764fe78cf034093699c675ed3073dce8323593f45fa07d29f95ba79dbba0e
BLAKE2b-256 checksum
How to use checksums
e5c56cb90b1cab4fbc469589c0a2f2fc3fbb84fd06fd46a768f6ad7654f33dd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL formualizer-0.9.2-cp310-abi3-musllinux_1_2_aarch64.whl
Size 8.2 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
b70e277168b42ad85072b11e6f76de8b865e4cff5fd6adfd8c50338222bfeee1
BLAKE2b-256 checksum
How to use checksums
51efac56c1682bc5c00f705ca8a0da4dcd06ab938e1f2246bebe5312cf781b1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL formualizer-0.9.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 8.4 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
7a4509acd0d9fc01fba67125d8e94256243a004f19a37008ce64fb48388b3b27
BLAKE2b-256 checksum
How to use checksums
1715005380d4d5e9aa407dcd06cb5a9c5d86c9caeafa3aa9e41b4b84bd1eba1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL formualizer-0.9.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 8.0 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
4bcef6694167767fca38d99a1cb307b483148a02a0aeb33a776a9ddd23d5ee23
BLAKE2b-256 checksum
How to use checksums
d2fb3340f0770fa916cf19f9a8297fa2f37f62f91d24ce17b05369b9829e16af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-macosx_11_0_arm64.whl

Download URL formualizer-0.9.2-cp310-abi3-macosx_11_0_arm64.whl
Size 7.7 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a576998746dc66e237b4602ebbb8591de6ca7a92b31d9f3f691b49052e6fee4b
BLAKE2b-256 checksum
How to use checksums
257a6a7ad318edaee1f8abc5b7d7c91aee877502e6916f0f10ca23af3dfe1fd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / formualizer-0.9.2-cp310-abi3-macosx_10_12_x86_64.whl

Download URL formualizer-0.9.2-cp310-abi3-macosx_10_12_x86_64.whl
Size 8.1 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
c0466ae8de59744e3bc99ad035cfd0fba7a685a0a248ab5769313d7c54416f2c
BLAKE2b-256 checksum
How to use checksums
141f5abd00d0dce39cc7ccbd31f28a7e30726bb892f694241d8ab031cd81c9ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.10.1

8 release files

0.10.0

8 release files

0.9.3

8 release files

This release

0.9.2 This release

8 release files

0.9.0

8 release files

0.8.4

8 release files

0.8.3

8 release files

0.8.2

8 release files

0.8.1

8 release files

0.8.0

8 release files

0.7.1

8 release files

0.7.0

8 release files

0.6.0

8 release files

0.5.9

8 release files

0.5.8

8 release files

0.5.7

8 release files

0.5.6

8 release files

0.5.5

8 release files

0.5.4

8 release files

0.5.3

8 release files

0.5.2

8 release files

0.5.1

8 release files

0.5.0

8 release files

0.4.3

8 release files

0.4.2

8 release files

0.4.1

8 release files

0.4.0

8 release files

0.3.5

8 release files

0.3.4

8 release files

0.3.3

8 release files

0.3.1

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page