Skip to main content

Embeddable spreadsheet engine — parse, evaluate & mutate Excel workbooks at native speed

Project description

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))

Load and save XLSX bytes

import formualizer as fz

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

out = wb.to_xlsx_bytes()

backend="umya" is currently the byte-oriented XLSX path. Path-based loading still defaults to calamine.

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.

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) -> 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)

formualizer ships a Pyodide-tagged wheel (*-pyodide_<abi>_wasm32.whl) alongside the native wheels on PyPI. Inside a Pyodide runtime:

import micropip
await micropip.install("formualizer")

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

Supported Pyodide versions: 0.29.x (ABI pyodide_2025_0). Later minors may require a new wheel — check the PyPI release matrix for your target Pyodide version.

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.
  • XLSX byte I/O (Workbook.to_xlsx_bytes, Workbook.from_bytes, load_workbook_bytes) uses the umya backend 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 that isn't on PyPI:

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

The build script derives Python, ABI, Emscripten, and Rust toolchain from pyodide config (no hardcoded versions), 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.

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

formualizer-0.7.1.tar.gz (1.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

formualizer-0.7.1-cp310-abi3-win_amd64.whl (6.3 MB view details)

Uploaded CPython 3.10+Windows x86-64

formualizer-0.7.1-cp310-abi3-musllinux_1_2_x86_64.whl (6.9 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

formualizer-0.7.1-cp310-abi3-musllinux_1_2_aarch64.whl (6.6 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

formualizer-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (6.7 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

formualizer-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (6.4 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

formualizer-0.7.1-cp310-abi3-macosx_11_0_arm64.whl (6.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

formualizer-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl (6.4 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file formualizer-0.7.1.tar.gz.

File metadata

  • Download URL: formualizer-0.7.1.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1.tar.gz
Algorithm Hash digest
SHA256 cb15f7693163e685fa81af87bd7ceec4896f985676cc834ab1c2d0f1997ed722
MD5 de0c49ad12e6fbbcb6790af64f5231f9
BLAKE2b-256 c281391ac6a5039821a159b438562a997b8007735c7a2bfb459e429e690d6d0e

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 6.3 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 b0ebad7b15f65c5d9005711bae5bfaba634eca87882949ce1c18351a4a9a8d89
MD5 fb8e274095e08fd86d68b9f13c0d30e0
BLAKE2b-256 9b09db4cf4c507e0e7eeb6ac17e0f2140ac3f7309940abe483fb0562f23ce11a

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 6.9 MB
  • Tags: CPython 3.10+, musllinux: musl 1.2+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 82636dee521d1b4447bdac3904d625c1362012541c7a1524eaa928381717e6fe
MD5 2048f30631c998279c0a8d7a63755dcd
BLAKE2b-256 0a758ed0172b66ef5f763976f5305ed959d9ad17ae5e81a1c48e87fe1a03b916

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-musllinux_1_2_aarch64.whl
  • Upload date:
  • Size: 6.6 MB
  • Tags: CPython 3.10+, musllinux: musl 1.2+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 b214ed635ec0cd6db5826506f2b0ca866c3caada29187954fc2b701337a2aa6d
MD5 7046ee48120235231fb8b19e11ab28c0
BLAKE2b-256 09e80762b65831520f42d34cc592b57d5dff53d036d814645e003c5fe769ab8f

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 6.7 MB
  • Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c1c31b9e7d23f061c15e7383ee8585812dc154d14d786761c8c7327bf1618200
MD5 07db4594a9e1de4b4f3a4b76f6554115
BLAKE2b-256 ca80c07b574b92bc12aa40445d4ecb269387c4163ec4291377f87e725c8f181e

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
  • Upload date:
  • Size: 6.4 MB
  • Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 7e11f63be60d20faf929d78077abd085a3a55bd2823ab18e890bdf433fe9e53d
MD5 4ec568a42b9bc46d04836b9cacfb5ef0
BLAKE2b-256 4d95e7c37a88ec33e66eab07d2cc2ffa24c431265bad00fb06f922582cb5d2ff

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 6.1 MB
  • Tags: CPython 3.10+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6187e7b0e691e31bb113cbd076f3c963fc1167605763f4c3d822967834027828
MD5 a9d41b7f0fbd6b256eccf93f0c37fc10
BLAKE2b-256 fc74a7f87014ea4639c2b4b971fdcd485ea1fbae19200a0b01b0f308fdbfd407

See more details on using hashes here.

File details

Details for the file formualizer-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

  • Download URL: formualizer-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl
  • Upload date:
  • Size: 6.4 MB
  • Tags: CPython 3.10+, macOS 10.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","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}

File hashes

Hashes for formualizer-0.7.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 d05ab7ddb4cc3f8b68991b03e3283d3def7860617c0c85b5459c2468b4b774d5
MD5 b76fb30216e34cdab4498e0625ddea8c
BLAKE2b-256 73d37c62c75ecb435360b4f795b76365bd0c1019e92ae3ddfcbd2dbb0bcca7d1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page