Skip to main content

FastFrame

CI Coverage

FastFrame finds slow pandas/NumPy idioms, rewrites only safe patterns deterministically, and proves equivalence before reporting performance.

Problem

Row-wise apply, iterrows, repeated DataFrame concatenation, and repeated np.append hide vectorizable work behind Python loops or repeated allocation. FastFrame scans Python files, directories, and notebook code cells; applies conservative LibCST rewrites; executes both versions in subprocesses; and compares their results and stdout. Runtime has no LLM or network dependency.

Install

End users

After FastFrame is published to PyPI, a user with Python 3.12 can install it without cloning this repository:

python -m pip install fastframe

This installs the fastframe command. For example:

fastframe scan my_script.py
fastframe fix my_script.py -o my_script_fast.py
fastframe verify my_script.py my_script_fast.py
fastframe bench my_script.py my_script_fast.py --runs 5

FastFrame executes supplied scripts during fix verification, verify, and bench; do not run it on untrusted scripts without a separate sandbox.

uv

For contributors working from the source repository:

uv sync --locked --extra test

Real output from this repository:

Resolved 31 packages in 29ms
Checked 30 packages in 13ms

Docker

docker build -t fastframe:buildathon .

The image runs as non-root user fastframe and has fastframe as its entrypoint. Run a scan with:

docker run --rm --network none fastframe:buildathon scan examples/sales_analysis_slow.py

Run the complete offline test command by overriding the CLI entrypoint:

docker run --rm --network none --entrypoint make fastframe:buildathon test

Terminal demo

$ uv run fastframe scan examples/sales_analysis_slow.py
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ Location             ┃ Pattern ┃ Explanation          ┃ Suggested action     ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ examples/sales_anal… │ AP001   │ row-wise             │ vectorize with       │
│                      │         │ DataFrame.apply      │ fastframe fix        │
│                      │         │ arithmetic           │                      │
│ examples/sales_anal… │ AP002   │ Series.apply         │ vectorize with       │
│                      │         │ arithmetic lambda    │ fastframe fix        │
│ examples/sales_anal… │ AP003   │ iterrows arithmetic  │ vectorize with       │
│                      │         │ append-to-list       │ fastframe fix        │
│ examples/sales_anal… │ AP004   │ growing DataFrame    │ vectorize with       │
│                      │         │ with concat/append   │ fastframe fix        │
│                      │         │ in a loop            │                      │
│ examples/sales_anal… │ AP005   │ growing NumPy array  │ vectorize with       │
│                      │         │ with np.append in a  │ fastframe fix        │
│                      │         │ loop                 │                      │
│ examples/sales_anal… │ AP006   │ downcastable         │ manual review        │
│                      │         │ object/int64/float64 │                      │
│                      │         │ column (manual       │                      │
│                      │         │ review)              │                      │
└──────────────────────┴─────────┴──────────────────────┴──────────────────────┘
EXIT_CODE=1

$ uv run fastframe fix examples/sales_analysis_slow.py -o build/sales_fast.py
Verification passed.
--- examples/sales_analysis_slow.py
+++ build/sales_fast.py
@@
-row_totals = work.apply(lambda row: row["units"] * row["price"], axis=1)
+row_totals = work["units"] * work["price"]

CLI

Scan

Scan a file, directory recursively, or notebook code cells:

uv run fastframe scan examples/sales_analysis_slow.py
uv run fastframe scan src --format json

scan --format json emits machine-readable findings and exits 1 when any finding exists, otherwise 0.

Inline suppressions are supported:

out = df["a"].apply(lambda x: x * 2)  # fastframe: ignore
out = df["b"].apply(lambda x: x * 2)  # fastframe: ignore[AP002]

Project configuration lives in pyproject.toml:

[tool.fastframe]
select = ["AP001", "AP002"]
ignore = ["AP006"]

Fix

Fix verifies by default and refuses to write if verification fails:

uv run fastframe fix examples/sales_analysis_slow.py -o build/sales_fast.py
uv run fastframe fix examples/sales_analysis_slow.py --diff
uv run fastframe fix examples/sales_analysis_slow.py --in-place
uv run fastframe fix examples/sales_analysis_slow.py -o build/sales_fast.py --no-verify

--in-place keeps sales_analysis_slow.py.bak. --no-verify prints a warning. Notebook fixes write rewritten code cells to a copy; Markdown cells are kept.

Verify

uv run fastframe verify examples/sales_analysis_slow.py build/sales_fast.py
uv run fastframe verify examples/sales_analysis_slow.py build/sales_fast.py --json

Real readable output:

PASS verification
┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┓
┃ Variable     ┃ Status ┃ Details    ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━┩
│ amounts      │ PASS   │ equivalent │
│ combined     │ PASS   │ equivalent │
│ row_totals   │ PASS   │ equivalent │
│ scaled_units │ PASS   │ equivalent │
│ values       │ PASS   │ equivalent │
└──────────────┴────────┴────────────┘
stdout: PASS

Verification reports original variables missing from the rewritten script as failures, compares captured stdout, uses the original script directory for relative paths and __file__, and has a 60-second subprocess timeout.

Bench

uv run fastframe bench examples/sales_analysis_slow.py build/sales_fast.py --runs 1
uv run fastframe bench examples/sales_analysis_slow.py build/sales_fast.py --runs 1 --json

Real output from one run on the 200k-row example:

┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
┃ Script    ┃ Median  ┃ Minimum ┃ Peak memory      ┃
┡━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
│ Original  │ 1.1700s │ 1.1700s │ 22,755,632 bytes │
│ Rewritten │ 0.0633s │ 0.0633s │ 22,753,741 bytes │
└───────────┴─────────┴─────────┴──────────────────┘
Speedup: 18.50x
Peak memory saved: +1,891 bytes (+0.01%)

Timing and memory are measured in separate child runs. Imports happen before timing, and each measurement path gets one warm-up run.

Report, rules, and explain

uv run fastframe report examples/sales_analysis_slow.py --html docs/index.html --runs 1
uv run fastframe rules
uv run fastframe explain AP003

report writes a self-contained HTML report. rules lists every built-in rule. explain AP003 prints its explanation and representative before/after.

Supported rules

AP001 — row-wise DataFrame arithmetic

# Before
out = df.apply(lambda row: row["a"] + row["b"], axis=1)
# After
out = df["a"] + df["b"]

AP002 — arithmetic Series lambda

# Before
out = df["a"].apply(lambda x: x * 2)
# After
out = df["a"] * 2

AP003 — iterrows() arithmetic append

# Before
for _, row in df.iterrows():
    out.append(row["a"] + row["b"])
# After
out.extend((df["a"] + df["b"]).tolist())

AP004 — DataFrame concat growth

# Before
for chunk in chunks:
    df = pd.concat([df, chunk], ignore_index=True)
# After
_fastframe_df_parts = [df]
for chunk in chunks:
    _fastframe_df_parts.append(chunk)
if len(_fastframe_df_parts) > 1:
    df = pd.concat(_fastframe_df_parts, ignore_index=True)

AP005 — np.append growth

# Before
for value in values:
    arr = np.append(arr, value)
# After
_fastframe_arr_parts = [arr]
for value in values:
    _fastframe_arr_parts.append(np.atleast_1d(value))
if len(_fastframe_arr_parts) > 1:
    arr = np.concatenate(_fastframe_arr_parts)

AP006 — downcast advisory

# Finding; not automatically rewritten
df["count"] = df["count"].astype("int64")
# Manual review suggestion
df["count"] = pd.to_numeric(df["count"], downcast="integer")

Results: 200k-row example

examples/sales_analysis_slow.py creates 200,000 rows with seeded RNG np.random.default_rng(2026). The report keeps the combined script as the whole-workload measurement, while each AP001–AP005 row uses its own focused 100k-row script from examples/patterns/. The following values all came from one --runs 1 report generation:

Benchmark Workload Speedup Verification
AP001 row-wise apply examples/patterns/ap001.py 147.32× PASS
AP002 Series apply examples/patterns/ap002.py 12.01× PASS
AP003 iterrows append examples/patterns/ap003.py 436.25× PASS
AP004 concat growth examples/patterns/ap004.py 6.23× PASS
AP005 np.append growth examples/patterns/ap005.py 5.88× PASS
AP006 downcast advisory no rewrite advisory only manual review
Whole sales workload examples/sales_analysis_slow.py (200k rows) 18.50× PASS

Benchmarks include subprocess execution and are machine/load dependent. The focused scripts are sized to make the slow form measurable; AP004 uses 2,000 prebuilt 500-row chunks and AP005 uses 20,000 appends. The HTML report contains the complete per-pattern timing, memory, code, diff, and badge details.

Safety guarantees

  • Only a strict arithmetic whitelist is rewritten: string-literal parameter subscripts, permitted bare parameters, numeric constants, unary minus, and + - * / // ** %.
  • Calls involving groupby, rolling, resample, expanding, or ewm are never automatically rewritten.
  • pd.concat rewrites require exactly [target, piece].
  • np.append rewrites require the target as the first argument and reject axis.
  • iterrows rewrites require _ as the index variable; index-dependent expressions are never rewritten.
  • AP006 is always advisory because safe downcasting depends on values, nullability, precision, and downstream consumers.
  • Rewrites preserve source formatting with LibCST and fix verifies before writing by default.

Limitations & security

  • verify and bench execute both supplied scripts with runpy in child processes. They are not a security sandbox.
  • Do not run untrusted scripts with FastFrame. Use a container or another sandbox with restricted filesystem, network, CPU, and memory access.
  • Verification compares matching top-level DataFrame, Series, ndarray, and list variables plus exact captured stdout. External side effects, databases, files, and network behavior are not equivalence guarantees.
  • Benchmarks measure complete script execution after imports, not an isolated function microbenchmark.
  • Unknown source shapes are reported only when recognized; no speculative rewrite is attempted.
  • Rewrites are syntax-based rather than type-aware: a method that looks like pandas apply on a non-pandas object can be rewritten. The default fix verification step executes both versions and catches mismatched behavior before writing the output.

Tests and quality

make test
uv run --no-sync ruff check .
uv run --no-sync mypy

Real local result:

90 passed
Required test coverage of 90% reached. Total coverage: 94.39%

GitHub Actions runs Ruff, mypy, coverage tests, Docker build, and the offline Docker test on pushes and pull requests.

Live demo

Open the FastFrame GitHub Pages report

Metadata

Release files for fastframe-pandas 0.1.0

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

Source distribution (sdist)

Source distribution for fastframe-pandas 0.1.0
File Size Uploaded Provenance
fastframe_pandas-0.1.0.tar.gz 37.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for fastframe-pandas 0.1.0
File Interpreter ABI Platform
fastframe_pandas-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.3 kB

Release files / fastframe_pandas-0.1.0.tar.gz

Download URL fastframe_pandas-0.1.0.tar.gz
Size 37.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3678c39d074b665506fc0cf58a0b894cf99d32e6d0831ab4c4a2ad2da9cbb1aa
BLAKE2b-256 checksum
How to use checksums
f915971ce894e8d64d38d134b218628f585360c65f7a5692d6e6eef40e4a63d5
Upload date
Uploaded using Trusted Publishing?
What is 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":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / fastframe_pandas-0.1.0-py3-none-any.whl

Download URL fastframe_pandas-0.1.0-py3-none-any.whl
Size 25.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b9d2cadd21171091a0125adeb7f5d58da774b8e3c76e1fb7742d21c987682c83
BLAKE2b-256 checksum
How to use checksums
d7f51750852f28e15f52691d299c234b67027da396abfdbaa80e1c9c062687da
Upload date
Uploaded using Trusted Publishing?
What is 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":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for fastframe-pandas 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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