Install formulas
masterInstall the core formulas package using pip. This provides the Excel formula interpreter and parser.
$ pip install formulasrepository·master·Indexed 19 days ago
https://github.com/vinci1it2000/formulasAn Excel formula interpreter for Python that parses, compiles, and executes Excel expressions. It allows for the compilation of entire workbooks into Python code via ExcelModel, enabling execution without the Excel COM server or Excel itself. The library includes a CLI for calculating workbooks, building JSON model representations, and testing spreadsheet accuracy, as well as a Flask-based API and GUI for serving models over HTTP.
Install the core formulas package using pip. This provides the Excel formula interpreter and parser.
$ pip install formulasTo enable full functionality, including Excel workbook compilation (ExcelModel) and plotting capabilities, install the all extra.
excel: Enables compiling and executing Excel workbooks via formulas.excel.ExcelModel.plot: Enables plotting the formula AST and the Excel model.$ pip install formulas[all]For automated workflows, you can use the formulas.cli module to run multiple calculation scenarios in a single batch.
name, an overwrite dictionary for inputs, and a renders list for outputs.python -m formulas.cli calc <workbook_path> --batch <batch_file_path>.Available CLI flags for batching:
--batch: Path to the JSON batch file.--processes: Number of processes to use for parallel execution.--output-format: Set to json to receive a summary of results.--output-dir: Directory where results will be saved.import json
import subprocess
import sys
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as tmp:
batch = Path(tmp) / 'batch.json'
batch.write_text(json.dumps([
{
'name': 'base',
'overwrite': {
"'[excel.xlsx]'!INPUT_A": 3,
"'[excel.xlsx]DATA'!B3": 1,
},
'renders': ["'[excel.xlsx]DATA'!C2=result"],
},
{
'name': 'stress',
'overwrite': {
"'[excel.xlsx]'!INPUT_A": 4,
"'[excel.xlsx]DATA'!B3": 1,
},
'renders': ["'[excel.xlsx]DATA'!C2=result"],
},
], indent=2))
result = subprocess.run([
sys.executable, '-m', 'formulas.cli', 'calc',
'test/test_files/excel.xlsx',
'--batch', str(batch),
'--processes', '2',
'--output-format', 'json',
'--output-dir', tmp,
], capture_output=True, text=True, check=False)
assert result.returncode == 0, result.stderr
summary = json.loads(result.stdout)
assert [item['name'] for item in summary] == ['base', 'stress']When using --overwrite in test or calc, values must follow these formats:
A1="my string".TRUE or FALSE (case-insensitive).YYYY-MM-DD format, e.g., A1=2024-12-31.10, 3.14).Use formulas.ExcelModel() to handle entire Excel files.
.loads("path/to/file.xlsx") followed by .finish() to prepare the model.circular=True to the .finish() method..calculate() to compute cell values..write() to save the calculated results back to a file.xl_model.dsp and use .plot(view=False) to visualize cell relationships.import formulas
# Load and finish the model
xl_model = formulas.ExcelModel().loads("../test/test_files/excel.xlsx").finish()
# Calculate and write results
xl_model.calculate()
xl_model.write()
# Plot dependency graph
dsp = xl_model.dsp
dsp.plot(view=False)You can embed formulas as a calculation engine within a lightweight web application using formulas.app.create_app. This allows you to load a workbook once and expose its calculation logic via a JSON API.
When using the API, you send a POST request to /api/calculate with a JSON body containing:
inputs: A dictionary mapping cell addresses (e.g., '[filename.xlsx]'!CELL') to their new values.renders: A list of strings defining the output mapping in the format '[filename.xlsx]SHEET'!CELL=variable_name.from formulas.app import create_app
app = create_app(files=('test/test_files/excel.xlsx',), circular=False)
client = app.test_client()
response = client.post('/api/calculate', json={
'inputs': {
"'[excel.xlsx]'!INPUT_A": 3,
"'[excel.xlsx]DATA'!B3": 1,
},
'renders': ["'[excel.xlsx]DATA'!C2=result"],
})
assert response.status_code == 200
assert response.get_json()['outputs'] == {'result': 10.0}You can treat an Excel workbook as a transformation step in a data pipeline. By using ExcelModel, you can compile a specific subset of inputs and outputs into a function that can be called repeatedly with structured data.
Workflow:
formulas.ExcelModel().loads(path).finish()..compile(inputs=[...], outputs=[...])..value).import formulas
model = formulas.ExcelModel().loads('test/test_files/excel.xlsx').finish()
func = model.compile(
inputs=["'[excel.xlsx]'!INPUT_A", "'[excel.xlsx]DATA'!B3"],
outputs=["'[excel.xlsx]DATA'!C2"],
)
records = [
{'id': 'row-1', 'input_a': 3, 'b3': 1},
{'id': 'row-2', 'input_a': 4, 'b3': 1},
]
results = []
for record in records:
result, = func(record['input_a'], record['b3'])
results.append({
'id': record['id'],
'result': result.value[0, 0],
})
assert results == [
{'id': 'row-1', 'result': 10.0},
{'id': 'row-2', 'result': 11.0},
]You can extend the formula interpreter by adding your own Python functions to the global function registry.
import formulas
# Get the current registry
FUNCTIONS = formulas.get_functions()
# Add a new function
FUNCTIONS['MYFUNC'] = lambda x, y: 1 + y + x
# Use it in a formula
func = formulas.Parser().ast('=MYFUNC(1, 2)')[1].compile()
print(func()) # Output: 4import formulas
FUNCTIONS = formulas.get_functions()
FUNCTIONS['MYFUNC'] = lambda x, y: 1 + y + x
func = formulas.Parser().ast('=MYFUNC(1, 2)')[1].compile()
print(func())Use formulas.ExcelModel to load, compile, and execute entire Excel workbooks without needing Excel installed.
import formulas
# Load and finish the model
xl_model = formulas.ExcelModel().loads('excel.xlsx').finish()
# Calculate the model
xl_model.calculate()
# Write results to a directory
xl_model.write(dirpath='output')Note: If your workbook contains circular references, pass circular=True to the .finish() method.
You can pass specific cell values as inputs and specify which cells should be returned as outputs in the calculate() method:
xl_model.calculate(
inputs={
"'[excel.xlsx]'!INPUT_A": 3, # Overwrite default
"'[excel.xlsx]DATA'!B3": 1 # Impose value
},
outputs=[
"'[excel.xlsx]DATA'!C2",
"'[excel.xlsx]DATA'!C4"
]
)import formulas
xl_model = formulas.ExcelModel().loads('excel.xlsx').finish()
xl_model.calculate()You can parse a raw Excel formula string into a compiled function using formulas.Parser().
To see the required inputs, use the .inputs attribute. You can also visualize the formula using .plot().
import formulas
# Parse and compile the formula
func = formulas.Parser().ast('=(1 + 1) + B3 / A2')[1].compile()
# Inspect inputs
print(list(func.inputs)) # ['A2', 'B3']
# Execute with values
result = func(1, 5)
print(result) # Array(7.0, dtype=object)
# Visualize (set view=True to open in browser)
func.plot(view=False)import formulas
func = formulas.Parser().ast('=(1 + 1) + B3 / A2')[1].compile()
func(1, 5)If you only need a subset of a workbook, use from_ranges to load a partial model based on specific output cells and ranges. This reduces the scope of the model to only what is necessary for those outputs.
import formulas
xl = formulas.ExcelModel().from_ranges(
"'[example.xlsx]DATA'!C2:D2", # Output range
"'[example.xlsx]DATA'!B4" # Output cell
)xl = formulas.ExcelModel().from_ranges("'[example.xlsx]DATA'!C2:D2", "'[example.xlsx]DATA'!B4")You can transform an ExcelModel into a high-performance DispatchPipe_ object (a callable function) by defining specific input and output cell references. This is useful for treating a spreadsheet like a standard Python function.
# Define inputs and outputs by cell reference
func = xl_model.compile(
inputs=[
"'[excel.xlsx]'!INPUT_A",
"'[excel.xlsx]DATA'!B3"
],
outputs=[
"'[excel.xlsx]DATA'!C2",
"'[excel.xlsx]DATA'!C4"
]
)
# Execute the function with positional arguments
# The return value is a list of results corresponding to the outputs
results = [v.value[0, 0] for v in func(3, 1)]
print(results) # e.g., [10.0, 4.0]func = xl_model.compile(
inputs=["'[excel.xlsx]'!INPUT_A", "'[excel.xlsx]DATA'!B3"],
outputs=["'[excel.xlsx]DATA'!C2", "'[excel.xlsx]DATA'!C4"]
)
results = [v.value[0, 0] for v in func(3, 1)]