Embeddable spreadsheet engine — parse, evaluate & mutate Excel workbooks at native speed
Project description
Formualizer for Python
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:
- Python Quickstart
- Python API Reference
- Function Reference — 320+ built-in functions
- SheetPort Guide — spreadsheets as typed APIs
- Workbook Edits and Batching
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))
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-spreadsheetimplementation.
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=Trueto 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}")
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
recalculate_file(path: str, output: str | None = None) -> dict
Core classes
Workbook— create, load, evaluate, undo/redo. Supportsfrom_path()andload_path()class methods.Sheet— per-sheet facade forset_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
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file formualizer-0.5.2.tar.gz.
File metadata
- Download URL: formualizer-0.5.2.tar.gz
- Upload date:
- Size: 993.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
860014df6d5858ab8be6a44897ca7c5e34f55705a1d4efb684c1aa8ae6c012bd
|
|
| MD5 |
f3578631d9971461ec4f0882c1e08bb8
|
|
| BLAKE2b-256 |
00ac65c313eb00c56713b294dbe41375b915a5e7f1315f511cdd73a22c9f4b45
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 5.7 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd16b8cc7a1f96af960ebb9368b840be0f5c13f976f9ea0dd0fb81f33b487c97
|
|
| MD5 |
c1b21a496ab47cfc7b0e56a581bd508d
|
|
| BLAKE2b-256 |
27b68e0b066986e232a8e8253ba167057d26f26b34daefb23d8b36773e83ba7e
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: formualizer-0.5.2-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.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68d5dff73a30a559cce882ea32b80eea11408fb3de977da97e2b6269aed02089
|
|
| MD5 |
9dc684189d574c076b03e35d531708b6
|
|
| BLAKE2b-256 |
adb2372b2caef2c4b351511d546c13137a0489161708732ede7535ae418f9ae7
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp310-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 6.7 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e67f0e1a070005267019b9a96cf2039855847eafde21a65672384846e3bec35
|
|
| MD5 |
2d4b83fea107b7267e60b9c0a92849d8
|
|
| BLAKE2b-256 |
508efe03bf58eb7686e95783ac6bf174ea1cdf0b8781e4bb803b80acc8a6052b
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 6.6 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3078c8e91de68f7ac1c0cb5ab0274dc52d5983955673a4934bd14969ca1c8ffe
|
|
| MD5 |
f386bacd14d74dbcd43bd398c1909260
|
|
| BLAKE2b-256 |
eab8c63bd9c66faec3649be10aca12a1bf47586a2ebb071097cca847afb9b575
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 6.0 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f37d251c78224698499537dcce6b44078f80793f57e900f41809867a459c45b1
|
|
| MD5 |
ba9ece7d562a9f46cf683bd33e7220a2
|
|
| BLAKE2b-256 |
67a7c1d25c6f2d7aa4ae26230095441110b559e330d0e2e137bac77ceda3b456
|
File details
Details for the file formualizer-0.5.2-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 6.1 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fcb36bb65f969db20b5a4c7a9fd9f67ea37d0a76e128fef52bb56ccf55f1ae69
|
|
| MD5 |
9fe651fba8805d430408b59b164338e9
|
|
| BLAKE2b-256 |
15c7b54bab802dd59264319a4cde5f6bfd1add1d4a7b4281a215333d02b25743
|
File details
Details for the file formualizer-0.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: formualizer-0.5.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 6.8 MB
- Tags: CPython 3.8, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ca1e6fd968091d274389830e165a0281f0667e0623fa69dadb62f1c268c7b35
|
|
| MD5 |
c2aa1b1676a121f36b73d51f5268d5af
|
|
| BLAKE2b-256 |
7ba583e01d39619b0bc5828f1a083a71ba2531d50c0b9bd1da722125a39ce91e
|