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 400+ 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 stable-ABI (abi3) wheels are published for Python 3.10 and newer on Linux (glibc and musl), 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
400+ 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, span_evaluation: bool | None = None) -> Workbook
load_workbook_bytes(data: bytes, strategy: str = None, backend: str | None = None, *, span_evaluation: bool | None = None) -> Workbook
recalculate_file(path: str, output: str | None = None) -> dict
recalculate_xlsx_bytes(data: bytes, *, error_location_limit: int | None = None) -> dict
recalculate_xlsx_file(path: str, output: str | None = None, *, error_location_limit: int | 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(), .time(), .datetime(), .duration(), .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.93.0 (the pinned release toolchain; edition 2024) 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. Both license texts ship inside the package.

Metadata

Release files for formualizer 0.9.3

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.3
File Size Uploaded
formualizer-0.9.3.tar.gz 2.3 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for formualizer 0.9.3
File
formualizer-0.9.3-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
formualizer-0.9.3-cp310-abi3-musllinux_1_2_x86_64.whl CPython 3.10 abi3 Linux musl 1.2+ x86-64 Details
formualizer-0.9.3-cp310-abi3-musllinux_1_2_aarch64.whl CPython 3.10 abi3 Linux musl 1.2+ ARM64 Details
formualizer-0.9.3-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.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
formualizer-0.9.3-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
formualizer-0.9.3-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 60.0 MB

Release files / formualizer-0.9.3.tar.gz

Download URL formualizer-0.9.3.tar.gz
Size 2.3 MB
Tags Source
SHA-256 checksum
How to use checksums
3d27562fff1310de5fc99699bf667a98cabf32fd7207e53e5c6c9bea8d94cf4c
BLAKE2b-256 checksum
How to use checksums
5bbeaf53c09a55d4f018a492d41f48413466bc7152a7717adcc767879f823834
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-win_amd64.whl

Download URL formualizer-0.9.3-cp310-abi3-win_amd64.whl
Size 8.0 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
13b3ee7c30f694055b549f5909832891518bc7335b69f94f8e9c48ab03380d32
BLAKE2b-256 checksum
How to use checksums
35d31601c35f27ad7b277fe268d86ba681f75693cfbc2840798483d94266e96d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-musllinux_1_2_x86_64.whl

Download URL formualizer-0.9.3-cp310-abi3-musllinux_1_2_x86_64.whl
Size 8.8 MB
Tags CPython 3.10 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
f78fd9e654b87671ebf3cf0c19b9639d3784ecd74c442e836f5aca56bfb23d8c
BLAKE2b-256 checksum
How to use checksums
f0791a3735cc6223779f6fac859e49150d52398b70e2fb32d5162296c92750ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-musllinux_1_2_aarch64.whl

Download URL formualizer-0.9.3-cp310-abi3-musllinux_1_2_aarch64.whl
Size 8.3 MB
Tags CPython 3.10 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
e1e6437d5d279fb7e0c77b6d6626bd2e652920cc249975cd239f486b9e86024f
BLAKE2b-256 checksum
How to use checksums
81883e804dbaccafe9f20872841b224d36600399c5b88fb9be1d513589bfa219
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL formualizer-0.9.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 8.6 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
13202ca184d005a8a676792beb0b6fe09b3aaac926e51594d6f04421a5e8cac5
BLAKE2b-256 checksum
How to use checksums
ea8b04d9689fb7f6fcb866634e67cd52cf50563aa8a2cd13d6ee7ef7f0c7bc8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL formualizer-0.9.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 8.1 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
8f4baba9b7ec73a4b8bca19231655c38d1ec71dcbc37be3e95cc2c129461a06c
BLAKE2b-256 checksum
How to use checksums
5f088ca2c2968b8c5300df4a7a7f57b1112514fd79c8d5fcd9daa0b31cc25894
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-macosx_11_0_arm64.whl

Download URL formualizer-0.9.3-cp310-abi3-macosx_11_0_arm64.whl
Size 7.8 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
20cc1c0830192efea0462862151a19bfca4c12a23f242839e2fa43ecc3bca1e6
BLAKE2b-256 checksum
How to use checksums
afe716c9cdbf243dafb3c780524cbfd651e79def90522cc163e0972cae1cc17d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3-cp310-abi3-macosx_10_12_x86_64.whl

Download URL formualizer-0.9.3-cp310-abi3-macosx_10_12_x86_64.whl
Size 8.2 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
4ba6254619eebcddb44a6c43dc3325d651a3556fee4897778edbf42b2a053ee7
BLAKE2b-256 checksum
How to use checksums
2ef21029a9897809735817b4df657ce85700209b13f8c13ec81dbabb34b0ec1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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

This release

0.9.3 This release

8 release files

0.9.2

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