Skip to content

Releases: jirikuncar/ffroute

v0.1.4 — FFAPIRouter as a static class

Choose a tag to compare

@jirikuncar jirikuncar released this 19 Jun 08:21

New: `ffroute.FFAPIRouter` — the dynamic FFRouter × APIRouter mixin that `speedup()` synthesized at runtime is now a real importable top-level class.

```python
from fastapi import FastAPI
from ffroute import FFAPIRouter

app = FastAPI()

... register routes ...

new_router = FFAPIRouter()
new_router.routes.extend(app.router.routes)
app.router = new_router
app.router._rebuild_index()
```

Why this matters

`FFAPIRouter` is the static type that backs an upcoming proposal to add a `router_class=` kwarg to `Starlette.init` and `FastAPI.init`. Once that lands upstream, the snippet above collapses to:

```python
app = FastAPI(router_class=FFAPIRouter)
```

Shipping the class now (in advance of the upstream PR) lets the proposal cite a real consumer.

Install

```bash
pip install 'ffroute[fastapi]'
```

(Or `uv add 'ffroute[fastapi]'`.) FastAPI is pulled in as a lazy optional dependency — `from ffroute import Router` / `FFRouter` still works without FastAPI installed.

Tests

3 new verification tests (importability + drop-in via assignment + works-without-speedup-call). `speedup()` itself is unchanged — the dynamic-mixin path stays as a fallback for arbitrary user-supplied router subclasses.

Migration

No breaking changes. Existing `speedup(app)` callers keep working unchanged.

v0.1.3

Choose a tag to compare

@jirikuncar jirikuncar released this 18 Jun 23:21

Fix: trie fast-path now engages inside mounted sub-apps

FFRouter.app matched the trie against scope['path'], but Starlette's Mount does not rewrite scope['path'] — it appends the consumed prefix to scope['root_path'] and leaves path as the full request path. A mounted FFRouter therefore fed the trie the full path (/outer/x) while its trie was built from relative patterns (/x), so every lookup missed and dispatch silently fell through to the slow super().app() linear scan.

This is the same dormancy class as the 0.1.2 middleware_stack fix: results stayed correct, but the fast-path went unused inside mounts.

Fix: use get_route_path(scope), which strips root_path — exactly what Route.matches uses internally. Includes a regression test.

Full Changelog: v0.1.2...v0.1.3

v0.1.2

Choose a tag to compare

@jirikuncar jirikuncar released this 18 Jun 22:46

Full Changelog: v0.1.1...v0.1.2

v0.1.1

Choose a tag to compare

@jirikuncar jirikuncar released this 18 Jun 14:21

First published release of ffroute — a Starlette/FastAPI-compatible URL route matcher implemented in Rust with PyO3.

100–200× faster than Starlette's linear regex scan on real-world OpenAPI specs (Stripe, GitHub, Kubernetes), with zero divergences against Starlette's own compile_path oracle.

Highlights

  • ffroute.Router — low-level trie matcher (zero Python deps).
  • ffroute.FFRouter — drop-in starlette.routing.Router subclass.
  • ffroute.speedup(app) — one-liner; recurses into Mount sub-apps by default; dynamic-mixin preserves FastAPI APIRouter hooks (incl. /openapi.json).
  • Wheels published for CPython 3.10–3.14 on Linux (manylinux + musllinux, x86_64 + aarch64), macOS (x86_64 + arm64) and Windows (x86_64).

Notes on v0.1.0

v0.1.0 was tagged but never produced wheels — its CI pipeline hung on a deprecated macOS runner. v0.1.1 ships on the fixed pipeline (macos-14 with cross-compiled Intel wheels; bumped action versions).

v0.1.0

Choose a tag to compare

@jirikuncar jirikuncar released this 18 Jun 14:04