Metadata-Version: 2.4
Name: fasterapi-rs
Version: 0.1.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Classifier: Framework :: FastAPI
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Requires-Dist: starlette>=0.37
Requires-Dist: pydantic>=2.0
Requires-Dist: uvicorn>=0.29 ; extra == 'bench'
Requires-Dist: httpx>=0.24 ; extra == 'bench'
Requires-Dist: psutil>=5.9 ; extra == 'bench'
Requires-Dist: uvicorn>=0.29 ; extra == 'standard'
Requires-Dist: httpx>=0.24 ; extra == 'standard'
Requires-Dist: jinja2>=3.1 ; extra == 'standard'
Requires-Dist: pytest>=8.0 ; extra == 'test'
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'test'
Requires-Dist: fastapi>=0.110 ; extra == 'test'
Requires-Dist: uvicorn>=0.29 ; extra == 'test'
Requires-Dist: httpx>=0.24 ; extra == 'test'
Requires-Dist: python-multipart>=0.0.9 ; extra == 'test'
Requires-Dist: websockets>=14.0 ; extra == 'test'
Requires-Dist: jinja2>=3.1 ; extra == 'test'
Provides-Extra: bench
Provides-Extra: standard
Provides-Extra: test
License-File: LICENSE
Summary: FastAPI drop-in powered by Rust — native Hyper/Tokio host, SIMD JSON, radix routing (PyO3)
Keywords: fastapi,web,rust,pyo3,performance
Author: Seyamalam
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/Seyamalam/fasterapi
Project-URL: Issues, https://github.com/Seyamalam/fasterapi/issues
Project-URL: Repository, https://github.com/Seyamalam/fasterapi

<p align="center">
  <img src="docs/assets/banner.svg" alt="fasterapi" width="680">
</p>

<p align="center">
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-00a08c.svg" alt="MIT license"></a>
  <img src="https://img.shields.io/badge/python-%E2%89%A53.10-3776ab.svg" alt="Python ≥3.10">
  <img src="https://img.shields.io/badge/tests-87%20passing-00a08c.svg" alt="87 tests passing">
  <img src="https://img.shields.io/badge/host-Rust%20Hyper-f97316.svg" alt="Rust Hyper host">
</p>

**FastAPI-compatible, Rust-powered.**

```python
# from fastapi import FastAPI, Depends, HTTPException, Query, Path, Body, APIRouter  # before
from fasterapi import FastAPI, Depends, HTTPException, Query, Path, Body, APIRouter  # after
```

`fasterapi` aims for FastAPI API and behavior compatibility: decorators,
`Path/Query/Header/Cookie/Body/Form`, Pydantic v2 validation, `Depends()` DI,
exception handlers, lifespan, middleware, `TestClient`, and OpenAPI. See the
migration guide for unsupported areas. When the compiled extension is
present, `app.serve()` runs on a native **Rust Hyper/Tokio host** — sockets,
HTTP parsing, and ASGI dispatch live in Rust and each request crosses into
Python exactly once (`FASTERAPI_UVICORN=1` falls back to uvicorn). Native
Rust components also accelerate selected primitives: `matchit` routing, JSON,
scalar validation, token-bucket rate limiting, and a compiled
dependency-graph representation. Targets Python ≥3.10. Free-threaded
builds remain experimental pending a compatible PyO3 configuration and
dedicated GIL-off validation.

> Can it be done? **Yes — incrementally.** v0.1 targets broad parity with an
> 87-test differential suite (byte-identical status/headers/bodies vs
> FastAPI) plus Rust primitives throughout (`RustRouter`, JSON helpers,
> validators, `TokenBucket`, `DependencyDAG`). The Hyper host is the default
> serving path when the extension is built: ~15µs of bridge overhead per
> request in-process, ~13k req/s serial keep-alive on loopback for a simple
> JSON endpoint — roughly 2× the previous Python-driven path.

---

## Contents

- [Install](#install)
- [Quickstart](#quickstart)
- [Parity (Phase 1)](#parity-phase-1)
- [Rust core (Phase 2)](#rust-core-phase-2)
- [Modern Python (Phase 3)](#modern-python-phase-3)
- [Testing & benchmarking (Phase 4)](#testing--benchmarking-phase-4)
- [Git hooks](#git-hooks)
- [Directory structure](#directory-structure)
- [Free-threading](#free-threading)
- [Benchmarks](#benchmarks)
- [Roadmap](#roadmap)
- [Changelog](CHANGELOG.md) · [Migration guide](docs/migration.md)
- [License](#license)

---

## Install

Prereqs: Python ≥3.10, Rust stable (for the extension), `uvicorn` for serving.

```bash
git clone https://github.com/Seyamalam/fasterapi && cd fasterapi
bash scripts/quickstart.sh        # venv + pip install -e ".[test,bench]" + hooks + tests
# or manually:
pip install -e ".[test,bench]"
pip install maturin && maturin develop --release   # Rust fast-paths (optional but recommended)
bash scripts/install_hooks.sh     # pre-push parity+perf gate, pre-commit fast checks
```

Pure-Python works without compiling — `_rust_bridge` falls back automatically
(`fasterapi.HAS_RUST` tells you which path is live).

## Quickstart

```python
from fasterapi import Depends, FastAPI, HTTPException, Query
from pydantic import BaseModel

app = FastAPI(title="demo")

class Item(BaseModel):
    name: str
    price: float

async def common(q: str | None = Query(default=None)):
    return {"q": q}

@app.get("/items/{item_id}")
async def read_item(item_id: int, common: dict = Depends(common)):
    if item_id == 0:
        raise HTTPException(status_code=404, detail="Not found")
    return {"item_id": item_id, **common}

@app.post("/items")
async def create_item(item: Item) -> Item:
    return item

if __name__ == "__main__":
    app.build()   # AOT: Depends DAG + Rust radix router + JSON warmup
    app.serve(port=8000)
```

`examples/quickstart.py` is runnable as-is. Serve with `uvicorn examples.quickstart:app`.

Fuller demo: [`examples/taskapi`](examples/taskapi/) is a complete backend
project (auth, CRUD, files, SSE, background jobs, tests) with its own README.

Side-by-side speed/parity audit vs real FastAPI: [`examples/comparison`](examples/comparison/)
runs two identical projects (one per framework) through a growing complexity
ladder — DB, auth, jobs, streaming — comparing outputs probe-by-probe and
RPS/latency per stage. The generated report ([`examples/comparison/AUDIT.md`](examples/comparison/AUDIT.md))
has already caught and fixed three parity bugs (422 `ctx`/`url` shape,
malformed-JSON-before-auth ordering, auto-`HEAD` on GET routes).

## Parity (Phase 1)

| Feature | FastAPI | fasterapi v0.1 |
|---|---|---|
| `@app.get/post/put/delete/patch/options/head/trace`, `@app.websocket` | ✅ | ✅ |
| `APIRouter(prefix, tags, deps)`, `include_router` | ✅ | ✅ |
| `Path/Query/Header/Cookie/Body/Form/File`, `Depends(use_cache, yield)` | ✅ | ✅ |
| Pydantic v2 validation + `response_model` + 422 `{"detail": [...]}` | ✅ | ✅ |
| `HTTPException`, `RequestValidationError`, `@app.exception_handler` | ✅ | ✅ |
| Lifespan `@asynccontextmanager`, `add_middleware`, CORS, GZip | ✅ | ✅ |
| `TestClient` (HTTPX semantics), OpenAPI `/openapi.json` | ✅ | ✅ |
| PEP 695 generics `def h[T: BaseModel](p: T) -> Page[T]` | ✅ (3.12+) | ✅ (native syntax, validated at runtime) |

Run the differential suite (same endpoints, both frameworks):

```bash
pytest tests/test_parity.py -q
pytest tests/ -q
```

### Differential results (auto-updated)

<!-- PARITY:START -->
> Auto-generated by `scripts/update_readme_tables.py` — do not edit by hand.

| Suite | Tests | Result |
|---|---|---|
| Differential (`tests/test_parity.py`, FastAPI vs fasterapi) | 26 | all passing |
| Differential edge cases (`tests/test_parity_extended.py`) | 23 | all passing |
| Native (`tests/test_fasterapi_native.py`) | 11 | all passing |
| Auth/background/encoders (`tests/test_parity_security.py`) | 8 | all passing |
| SSE/docs-adjacent (`tests/test_parity_surface.py`) | 7 | all passing |
| Export guard (`tests/test_exports.py`) | 3 | all passing |
| Thread soak (`tests/test_threads.py`) | 3 | all passing |
| Hyper host wire (`tests/test_hyper_host.py`) | 6 | all passing |
| **Total** | **87** | **87 passed** |

Coverage: ping, path/query/header/cookie params, validation shapes (422 `loc`/`type`/`ctx`, multi-error, malformed-JSON `json_invalid`), JSON body + `response_model` (+flags, return-annotation fallback), Form, file upload, DI order + `yield` teardown, `HTTPException`, custom handlers, router prefixes, OpenAPI shapes (full-spec equality, securitySchemes, webhooks), websockets (+auth), guards, DAG, Rust bridge, OAuth2/HTTP/API-key schemes, `Security`, password-grant forms, background tasks, datetime/UUID/Enum encoding, export surface, thread soak, Hyper host (file bytes, 413, middleware fallback, graceful lifespan, ws echo/auth).
<!-- PARITY:END -->

## Rust core (Phase 2)

`src/lib.rs` → `fasterapi._fasterapi_rs` (PyO3):

- **`RustRouter`** — `matchit` radix tree; live dispatch via `FastRouter`:
  unambiguous FULL matches resolve natively, anything it can't prove
  identical (mounts, convertors, ordering ambiguity, method mismatch) falls
  back to Starlette's scan.
- **`fast_json_loads/dumps`** — `simd-json` + `serde_json`, `py.allow_threads`
  so worker threads never block on the GIL.
- **`validate_int/float/bool/str`** — strict scalar coercion + constraints
  (`ge/le/gt/lt`, lengths, regex) for query/path/header/form, zero-GIL.
- **`TokenBucket`** — thread-safe `governor` token bucket. `RateLimiter` is
  a pure-ASGI middleware; on the Hyper host the bucket is checked in Rust
  before any Python runs (true socket-level rejection).
- **`DependencyDAG`** — `topological_sort()` + `concurrent_levels()` (Kahn)
  expose a compiled plan. Runtime dependency solving remains in Python and is
  currently sequential; the levels are not yet used to fan out dependencies.
- **Host helpers** — `hyper_health()`, `tokio_worker_count()`,
  `process_rss_bytes()`, `serve_static_config()` (sendfile plan).

```python
app.build()  # compile DAG + router once at startup
from fasterapi import _rust_bridge as b
b.fast_json_loads('{"a":1}')   # SIMD when compiled
```

The inverted runtime is the default host when the extension is built
(`FASTERAPI_UVICORN=1` opts back out to uvicorn; `fasterapi run --hyper`
forces it on). Hyper owns the socket and HTTP parsing; each request crosses
into Python exactly once through a cached bridge — Rust builds the ASGI
scope natively and stream sessions live in a Rust registry. Requests bodies
are capped at 16 MiB (`FASTERAPI_MAX_BODY` tunes it). If your workload
behaves differently, `FASTERAPI_UVICORN=1` keeps the uvicorn path
byte-for-byte available.

## Modern Python (Phase 3)

- **PEP 695 generics** — plain `async def h[T: BaseModel](payload: T)` works;
  validation goes through `TypeAdapter(T)` like any model.
- **Edge guards** — `RateLimiter` uses the native token-bucket primitive when
  compiled, but middleware dispatch itself remains in Python. `APIKeyGuard`
  is a Python middleware.
- **File serving** — under the Rust host, `FileResponse` can attach an
  internal sendfile marker for whole-file responses. Range requests and
  uvicorn use Starlette's Python file-response path.

## Testing & benchmarking (Phase 4)

```bash
# parity (must be 100% green)
pytest tests/test_parity.py tests/test_fasterapi_native.py -q

# micro fast-paths only (no servers)
python benchmarks/benchmark_differential.py --micro-only

# full differential load (spins up :8011 fastapi+uvicorn vs :8012 fasterapi)
python benchmarks/benchmark_differential.py --quick
python benchmarks/benchmark_differential.py --requests 20000 --concurrency 100 --min-speedup 1.0
python benchmarks/benchmark_differential.py --quick --min-speedup 3.0  # strict gate
```

Metrics: RPS, p50/p90/p99/mean/max, CPU %, RSS — JSON to
`benchmark-results/differential-latest.json`. Uses `oha`/`wrk`/`bombardier`
when installed, else a portable asyncio+httpx loader. Exit `2` on gate failure.

`python scripts/update_readme_tables.py` refreshes the auto-generated tables
below (parity + micro + last macro run); the pre-commit hook runs it on every
commit so each push carries fresh numbers.

## Git hooks

No GitHub Actions — everything runs locally before it can leave your machine.

```bash
bash scripts/install_hooks.sh   # sets core.hooksPath=.githooks
```

- **`pre-push`**: `maturin develop --release` → full parity suite (`tests/`,
  reject on any failure) → micro fast-path gate (strict **3x**) → macro load
  gate (wide band **x0.80**, catches real regressions through loopback noise;
  gate writes go to scratch JSON so committed bench data is untouched;
  `FASTERAPI_SKIP_PERF=1` to override).
- **`pre-commit`**: `cargo fmt --check` (advisory) + README table refresh +
  fast native unit tests.

## Directory structure

```
fasterapi/
├── Cargo.toml                  # Rust ext: pyo3, matchit, simd-json, serde, governor, regex + hyper/tokio host
├── pyproject.toml              # maturin (pyo3 bindings, python-source=python) + `fasterapi` script
├── rust-toolchain.toml
├── src/lib.rs                  # PyO3 core: Hyper host, router, validators, SIMD JSON, guards, DAG
├── python/fasterapi/
│   ├── __init__.py             # drop-in surface (FastAPI, APIRouter, Depends, …)
│   ├── applications.py         # FastAPI(Starlette): decorators, lifespan, openapi, build(), serve()
│   ├── routing.py              # APIRouter + validation + DI solver + ws + SSE
│   ├── params.py               # Path/Query/Header/Cookie/Body/Form/File/Depends/Security
│   ├── security.py             # OAuth2/HTTP/API-key schemes + forms
│   ├── openapi/                # docs.py (Swagger/ReDoc) + utils.py (get_openapi)
│   ├── sse.py                  # EventSourceResponse, ServerSentEvent
│   ├── responses.py            # + FileResponse(sendfile), ORJSON/UJSON shims
│   ├── encoders.py             # jsonable_encoder
│   ├── middleware/             # cors/gzip/trustedhost/… + asyncexitstack
│   ├── _hyper_serve.py         # Python side of the Hyper host (ASGI drive)
│   ├── _rust_bridge.py         # HAS_RUST switch + pure-Python fallbacks
│   ├── cli.py                  # `fasterapi run` (+ __main__.py)
│   └── py.typed                # PEP 561: typed package
├── tests/                      # differential suites + guards + thread soak + hyper host
├── benchmarks/
│   └── benchmark_differential.py
├── scripts/                    # update_readme_tables, install_hooks, quickstart, free-threaded-check
├── docs/                     # migration.md, hyper-gates.md, assets/ (logo)
├── CHANGELOG.md
└── examples/quickstart.py
```

## Free-threading

Several Rust parsing and validation routines release the GIL with
`py.allow_threads`; shared native router state is protected by locks. Python
request state is local, and shared reflection/adapter caches use locks. This
is not, by itself, proof of full free-threaded compatibility: the extension
must be built with a compatible PyO3/free-threaded configuration and tested on
a `--disable-gil` interpreter before that runtime can be considered supported.

## Benchmarks

Numbers below are regenerated on every commit (`scripts/update_readme_tables.py`
via pre-commit; macro rows reflect the last full bench run on the committer's
machine, so hardware cancels out in the gate).

The recorded macro row is a single loopback run — treat it as indicative
rather than definitive, since local measurements vary substantially.
Validation-heavy handlers also remain Python-bound, so moving socket parsing
to Rust does not remove Python/Pydantic or GIL costs. Re-run the
differential benchmark on the target workload before drawing conclusions.
The Hyper host's Python worker pool is configurable with `FASTERAPI_POOL`;
its default is the CPU count. Per-request bridge overhead measured ~15µs
in-process after the GIL-crossing batching work (was ~82µs).

<!-- BENCH:START -->
> Auto-generated by `scripts/update_readme_tables.py` — do not edit by hand. Engine: `Rust ext`.

Micro fast-paths (in-process, no servers):

| Check | Baseline | fasterapi | Speedup |
|---|---|---|---|
| JSON `loads` | 0.0267s stdlib | 0.0266s (delegates to stdlib — SIMD parse loses to CPython's fused scanner) | x1.0 |
| JSON `dumps` | stdlib | Rust direct-writer | x2.56 |
| Model validate (`loads`+`validate_python` vs `validate_json`) | 0.0027s | 0.0013s | x2.01 |
| Model dump (`dump_python`+`dumps` vs `dump_json`) | 0.0037s | 0.0013s | x2.84 |
| Router match | 0.1714s Python | 0.066s `matchit` | x2.6 |

Macro load (loopback, httpx concurrency; last full run):

Run: `2026-11-10`, host `hyper`, speedup **x1.35** (pre-push: micro strict 3x + macro wide band x0.80).

| Server | RPS | p50 | p90 | p99 | RSS |
|---|---|---|---|---|---|
| fastapi+uvicorn | 397.1 | 33.78ms | 86.821ms | 141.864ms | 58.4MB |
| fasterapi | 536.0 | 25.917ms | 70.447ms | 149.979ms | 52.7MB |
<!-- BENCH:END -->

Design rule going forward: **as much as possible in Rust,
type-safe, lighter**. Rust primitives are available for routing, query parsing,
scalar validation, JSON, rate limiting, and Hyper file serving; current ASGI
dispatch and dependency resolution still have Python/Starlette work. Measure
each integration end to end rather than inferring request performance from
isolated microbenchmarks. `HAS_RUST` + `--micro-only` check extension
availability and primitive behavior, not end-to-end speed.

`FASTERAPI_MIN_SPEEDUP=3.0` enables the strict microbenchmark gate for the
native router primitive; this is not an end-to-end request dispatch gate.

## Roadmap

- [x] Phase 1 — drop-in parity + TestClient + OpenAPI
- [x] Phase 2 — Rust DAG/router/SIMD JSON/guards primitives + `app.build()`
- [x] Phase 3 — PEP 695, edge guards, sendfile `FileResponse`
- [x] Phase 4 — differential tests + benchmark engine + pre-push gate
- [x] Hyper host promotion — default backend when the extension is built (uvicorn opt-out via `FASTERAPI_UVICORN=1`)
- [ ] Hyper host hardening — permessage-deflate refusal, HTTP/2 decision, long soak runs (see [docs/hyper-gates.md](docs/hyper-gates.md))
- [ ] Fan-out of `concurrent_levels()` for async deps (sequential `await` today)
- [ ] `3.13t` wheel CI (local maturin only — no Actions per repo policy)

## License

MIT — see [LICENSE](LICENSE).

