fasterapi-rs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fasterapi_rs-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
- fasterapi_rs-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
- fasterapi_rs-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- fasterapi_rs-0.1.0/.gitignore +37 -0
- fasterapi_rs-0.1.0/CHANGELOG.md +140 -0
- fasterapi_rs-0.1.0/CONTRIBUTING.md +65 -0
- fasterapi_rs-0.1.0/Cargo.lock +1102 -0
- fasterapi_rs-0.1.0/Cargo.toml +42 -0
- fasterapi_rs-0.1.0/LICENSE +21 -0
- fasterapi_rs-0.1.0/PKG-INFO +393 -0
- fasterapi_rs-0.1.0/README.md +347 -0
- fasterapi_rs-0.1.0/SECURITY.md +30 -0
- fasterapi_rs-0.1.0/benchmarks/apps.py +10 -0
- fasterapi_rs-0.1.0/benchmarks/benchmark_differential.py +468 -0
- fasterapi_rs-0.1.0/docs/assets/banner.svg +33 -0
- fasterapi_rs-0.1.0/docs/assets/logo.svg +26 -0
- fasterapi_rs-0.1.0/docs/hyper-gates.md +57 -0
- fasterapi_rs-0.1.0/docs/migration.md +93 -0
- fasterapi_rs-0.1.0/examples/comparison/AUDIT.md +112 -0
- fasterapi_rs-0.1.0/examples/comparison/README.md +63 -0
- fasterapi_rs-0.1.0/examples/comparison/STAGES.md +41 -0
- fasterapi_rs-0.1.0/examples/comparison/audit.py +635 -0
- fasterapi_rs-0.1.0/examples/comparison/fastapi_app.py +367 -0
- fasterapi_rs-0.1.0/examples/comparison/fasterapi_app.py +378 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/__init__.py +12 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/db.py +146 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/schemas.py +41 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/security.py +27 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/settings.py +42 -0
- fasterapi_rs-0.1.0/examples/comparison/stack/stack.yaml +3 -0
- fasterapi_rs-0.1.0/examples/comparison/static/hello.txt +1 -0
- fasterapi_rs-0.1.0/examples/quickstart.py +34 -0
- fasterapi_rs-0.1.0/examples/taskapi/README.md +68 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/core/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/core/config.py +35 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/core/deps.py +40 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/core/security.py +36 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/db/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/db/store.py +111 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/main.py +58 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/models/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/models/schemas.py +67 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/routers/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/routers/auth.py +29 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/routers/files.py +40 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/routers/stream.py +28 -0
- fasterapi_rs-0.1.0/examples/taskapi/app/routers/tasks.py +68 -0
- fasterapi_rs-0.1.0/examples/taskapi/requirements.txt +5 -0
- fasterapi_rs-0.1.0/examples/taskapi/tests/__init__.py +0 -0
- fasterapi_rs-0.1.0/examples/taskapi/tests/test_api.py +92 -0
- fasterapi_rs-0.1.0/pyproject.toml +78 -0
- fasterapi_rs-0.1.0/python/fasterapi/__init__.py +99 -0
- fasterapi_rs-0.1.0/python/fasterapi/__main__.py +8 -0
- fasterapi_rs-0.1.0/python/fasterapi/_fast_router.py +155 -0
- fasterapi_rs-0.1.0/python/fasterapi/_hyper_serve.py +702 -0
- fasterapi_rs-0.1.0/python/fasterapi/_rust_bridge.py +368 -0
- fasterapi_rs-0.1.0/python/fasterapi/_version.py +3 -0
- fasterapi_rs-0.1.0/python/fasterapi/applications.py +386 -0
- fasterapi_rs-0.1.0/python/fasterapi/background.py +11 -0
- fasterapi_rs-0.1.0/python/fasterapi/cli.py +145 -0
- fasterapi_rs-0.1.0/python/fasterapi/concurrency.py +42 -0
- fasterapi_rs-0.1.0/python/fasterapi/dag.py +117 -0
- fasterapi_rs-0.1.0/python/fasterapi/datastructures.py +47 -0
- fasterapi_rs-0.1.0/python/fasterapi/dependencies.py +6 -0
- fasterapi_rs-0.1.0/python/fasterapi/encoders.py +49 -0
- fasterapi_rs-0.1.0/python/fasterapi/exception_handlers.py +37 -0
- fasterapi_rs-0.1.0/python/fasterapi/exceptions.py +135 -0
- fasterapi_rs-0.1.0/python/fasterapi/guards.py +95 -0
- fasterapi_rs-0.1.0/python/fasterapi/logger.py +7 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/__init__.py +7 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/asyncexitstack.py +35 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/cors.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/gzip.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/httpsredirect.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/trustedhost.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/middleware/wsgi.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/openapi/__init__.py +7 -0
- fasterapi_rs-0.1.0/python/fasterapi/openapi/constants.py +7 -0
- fasterapi_rs-0.1.0/python/fasterapi/openapi/docs.py +213 -0
- fasterapi_rs-0.1.0/python/fasterapi/openapi/utils.py +454 -0
- fasterapi_rs-0.1.0/python/fasterapi/openapi/webhooks.py +8 -0
- fasterapi_rs-0.1.0/python/fasterapi/param_functions.py +19 -0
- fasterapi_rs-0.1.0/python/fasterapi/params.py +205 -0
- fasterapi_rs-0.1.0/python/fasterapi/py.typed +1 -0
- fasterapi_rs-0.1.0/python/fasterapi/requests.py +6 -0
- fasterapi_rs-0.1.0/python/fasterapi/responses.py +120 -0
- fasterapi_rs-0.1.0/python/fasterapi/routing.py +1715 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/__init__.py +375 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/api_key.py +12 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/base.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/http.py +18 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/oauth2.py +16 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/open_id_connect_url.py +9 -0
- fasterapi_rs-0.1.0/python/fasterapi/security/utils.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/sse.py +126 -0
- fasterapi_rs-0.1.0/python/fasterapi/staticfiles.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/status.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/templating.py +5 -0
- fasterapi_rs-0.1.0/python/fasterapi/testclient.py +20 -0
- fasterapi_rs-0.1.0/python/fasterapi/types.py +10 -0
- fasterapi_rs-0.1.0/python/fasterapi/utils.py +118 -0
- fasterapi_rs-0.1.0/python/fasterapi/websockets.py +7 -0
- fasterapi_rs-0.1.0/rust-toolchain.toml +2 -0
- fasterapi_rs-0.1.0/scripts/free-threaded-check.sh +37 -0
- fasterapi_rs-0.1.0/scripts/install_hooks.sh +9 -0
- fasterapi_rs-0.1.0/scripts/quickstart.sh +14 -0
- fasterapi_rs-0.1.0/scripts/soak-hyper.sh +101 -0
- fasterapi_rs-0.1.0/scripts/update_readme_tables.py +276 -0
- fasterapi_rs-0.1.0/src/lib.rs +2223 -0
- fasterapi_rs-0.1.0/tests/__init__.py +0 -0
- fasterapi_rs-0.1.0/tests/test_exports.py +166 -0
- fasterapi_rs-0.1.0/tests/test_fasterapi_native.py +225 -0
- fasterapi_rs-0.1.0/tests/test_hyper_host.py +341 -0
- fasterapi_rs-0.1.0/tests/test_parity.py +729 -0
- fasterapi_rs-0.1.0/tests/test_parity_extended.py +471 -0
- fasterapi_rs-0.1.0/tests/test_parity_security.py +314 -0
- fasterapi_rs-0.1.0/tests/test_parity_surface.py +170 -0
- fasterapi_rs-0.1.0/tests/test_threads.py +81 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a behavioral difference from FastAPI or a crash
|
|
4
|
+
title: ""
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Environment**
|
|
9
|
+
|
|
10
|
+
- fasterapi version / commit:
|
|
11
|
+
- Python version: <!-- e.g. 3.14.7 -->
|
|
12
|
+
- Rust extension built? (`python -c "import fasterapi; print(fasterapi.HAS_RUST)"`):
|
|
13
|
+
- Host: <!-- hyper (default) / uvicorn (FASTERAPI_UVICORN=1) / TestClient -->
|
|
14
|
+
|
|
15
|
+
**What happened vs what FastAPI does**
|
|
16
|
+
|
|
17
|
+
<!-- For parity bugs, a minimal app reproducing both behaviors is ideal -->
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# minimal reproduction
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Expected output**
|
|
24
|
+
|
|
25
|
+
**Actual output**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Missing FastAPI surface or a new capability
|
|
4
|
+
title: ""
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**FastAPI API or behavior you need**
|
|
9
|
+
|
|
10
|
+
<!-- Link to FastAPI docs if it exists upstream -->
|
|
11
|
+
|
|
12
|
+
**Use case**
|
|
13
|
+
|
|
14
|
+
**Willing to implement it?**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
<!-- What changed and why -->
|
|
4
|
+
|
|
5
|
+
## Verification
|
|
6
|
+
|
|
7
|
+
- [ ] `pytest tests/ -q` — all differential tests green
|
|
8
|
+
- [ ] `maturin develop --release` after any `src/` change
|
|
9
|
+
- [ ] Parity vs FastAPI verified for behavioral changes
|
|
10
|
+
- [ ] Benchmarked if perf-sensitive (`benchmarks/benchmark_differential.py`)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
target/
|
|
11
|
+
*.so
|
|
12
|
+
*.pyd
|
|
13
|
+
|
|
14
|
+
# Maturin / Rust
|
|
15
|
+
/target/
|
|
16
|
+
Cargo.lock
|
|
17
|
+
|
|
18
|
+
# Test / bench artifacts
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.hypothesis/
|
|
21
|
+
benchmark-results/
|
|
22
|
+
*.prof
|
|
23
|
+
examples/comparison/audit-results/
|
|
24
|
+
examples/comparison/audit.db
|
|
25
|
+
examples/comparison/audit_*.db
|
|
26
|
+
examples/comparison/audit_*.db-*
|
|
27
|
+
audit_fastapi.db*
|
|
28
|
+
audit_fasterapi.db*
|
|
29
|
+
|
|
30
|
+
# OS / editors
|
|
31
|
+
.DS_Store
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
*.swp
|
|
35
|
+
|
|
36
|
+
# Env
|
|
37
|
+
.env
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to fasterapi. Format follows Keep a Changelog; versions
|
|
4
|
+
are `0.x` until the Hyper host promotes to default.
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Radix-tree dispatch is live**: `FastRouter` compiles all plain routes
|
|
10
|
+
into `matchit` and answers unambiguous FULL matches in Rust; ambiguous
|
|
11
|
+
orderings, Mount/Host entries, convertor patterns, method mismatches and
|
|
12
|
+
redirects defer to Starlette's scan — byte-identical semantics.
|
|
13
|
+
- **DI fan-out**: yield-free `Depends` subtrees resolve concurrently
|
|
14
|
+
(signature-ordered results, per-dep locks for `use_cache`, deterministic
|
|
15
|
+
first-exception-wins). Yield deps stay sequential to preserve teardown
|
|
16
|
+
order. Dependency validation errors now collect across all siblings like
|
|
17
|
+
FastAPI (`{"detail": [...]}` lists every failure).
|
|
18
|
+
- **Fast JSON writer** (`itoa`/`ryu`, byte-wise escaping, Python-`repr`
|
|
19
|
+
float adapter): `fast_json_dumps` ~x2.5 vs compact stdlib, byte-identical
|
|
20
|
+
output (`ensure_ascii=False` contract, coerced-key quoting, `strict=`
|
|
21
|
+
mirrors `allow_nan=False`). Wired into the default response path via
|
|
22
|
+
`_FastJSONResponse`.
|
|
23
|
+
- **Socket-level rate limiting on Hyper**: `RateLimiter` is now a pure-ASGI
|
|
24
|
+
middleware (no `Request` allocation); its bucket is registered on the app
|
|
25
|
+
and checked in Rust before any Python runs (`429` pre-parse). Under
|
|
26
|
+
uvicorn the same pure-ASGI path applies.
|
|
27
|
+
- **Route kwargs**: `include_in_schema`, `openapi_extra`, `callbacks`,
|
|
28
|
+
`responses` (route-level merge over router default) — all wired into
|
|
29
|
+
`get_openapi`.
|
|
30
|
+
- **`app.mount()`** verified for ASGI sub-apps and `StaticFiles`.
|
|
31
|
+
- **`on_event("startup"|"shutdown")`** on `FastAPI`/`APIRouter` with merge
|
|
32
|
+
on `include_router` (deprecation-warned, matches FastAPI semantics).
|
|
33
|
+
- **`fasterapi.openapi.webhooks`** module (import-surface parity).
|
|
34
|
+
- **`FASTERAPI_MAX_BODY`** env var tunes the Hyper body cap (16 MiB default).
|
|
35
|
+
- **Free-threaded support**: the extension declares `gil_used = false`
|
|
36
|
+
(PyO3 nogil-safe) — the full 87-test suite passes under `PYTHON_GIL=0`
|
|
37
|
+
on CPython 3.14t.
|
|
38
|
+
- **True streaming on Hyper**: responses that keep producing past a 30ms
|
|
39
|
+
settle graduate to a real-time session — chunks flow through an mpsc into
|
|
40
|
+
Hyper's body writer (chunked encoding), the client sees incremental bytes,
|
|
41
|
+
and client disconnect cancels the app task. `receive()` now correctly
|
|
42
|
+
pends for disconnect instead of returning it eagerly.
|
|
43
|
+
- **WebSocket subprotocol negotiation** on Hyper: `sec-websocket-protocol`
|
|
44
|
+
offers reach `scope["subprotocols"]`; the app's `accept(subprotocol=...)`
|
|
45
|
+
is echoed in the 101.
|
|
46
|
+
- Hyper host gate run: ~x1.03 uvicorn RPS on a micro-JSON workload
|
|
47
|
+
(5453 vs 5316 rps loopback) — first parity-or-better measurement.
|
|
48
|
+
- GitHub Actions CI (test matrix 3.10–3.14 + lint + wheel builds on tags)
|
|
49
|
+
and a ruff advisory gate in `pre-commit`.
|
|
50
|
+
- `tests/test_parity_extended.py`: 23 differential tests — multi-dep error
|
|
51
|
+
collection, required params, DI concurrency/cache/ordering, mounts,
|
|
52
|
+
on_event, route kwargs, enum/union/nested-model validation, route
|
|
53
|
+
ordering edge cases, rate limiter, JSON writer parity.
|
|
54
|
+
|
|
55
|
+
### Performance
|
|
56
|
+
- **GIL-crossing batching on the Hyper host**: one Python crossing per
|
|
57
|
+
request — Rust builds the ASGI scope + `http.request` natively and hands
|
|
58
|
+
them to a cached `run_scoped` bridge callable (`OnceLock`, no per-request
|
|
59
|
+
import/getattr/`clone_ref`); the app handle is `Arc<PyObject>` and stream
|
|
60
|
+
sessions live in a Rust registry keyed by `u64`. Python-side pump drives
|
|
61
|
+
the loop via `run_forever()` + `loop.stop()` wakes with a no-poll selector
|
|
62
|
+
shim, removing ~52µs/request of `kqueue.control` syscalls. In-process
|
|
63
|
+
`run_request` ~82µs → ~15µs; serial keep-alive ~146µs → ~69µs median
|
|
64
|
+
(≈6.4k → ≈13.2k rps). `run_request` remains as a scope-building compat
|
|
65
|
+
wrapper.
|
|
66
|
+
- **`AsyncExitStackMiddleware` → pure ASGI**: the request-lifetime stack is
|
|
67
|
+
now set inline without `BaseHTTPMiddleware` (no `Request` alloc, task
|
|
68
|
+
group, or in-memory streams per request). ASGI-level micro bench:
|
|
69
|
+
~x1.25 vs fastapi (12.2k vs 9.7k req/s) — previously ~x0.45.
|
|
70
|
+
- **`_solve_plan`**: per-function compiled param dispatch (kind-tagged
|
|
71
|
+
plan entries, FastAPI `dependant`-style) cached in a weakref dict —
|
|
72
|
+
no more per-request signature classification.
|
|
73
|
+
- **No `dict()` materialization** on `query_params`/`headers`/`cookies` —
|
|
74
|
+
Starlette's MultiDict types already provide the needed interface.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
- **Sync endpoints ran inline on the event loop** — any blocking handler
|
|
78
|
+
stalled all concurrent requests. Now `run_in_threadpool` like FastAPI.
|
|
79
|
+
- **Required `Query`/`Header`/`Cookie` params** silently resolved to
|
|
80
|
+
`Ellipsis` (dead `param.default is empty` check) — now 422 correctly.
|
|
81
|
+
- **Multi-dep 422s** collected only the first failing dep's errors —
|
|
82
|
+
now returns every failure in signature order like FastAPI.
|
|
83
|
+
|
|
84
|
+
### Added (previous)
|
|
85
|
+
- Rust Hyper + Tokio ASGI host spike (`serve_asgi`, `FASTERAPI_HYPER=1`,
|
|
86
|
+
`python -m fasterapi run app:app --hyper`): sockets + HTTP parsing in Rust,
|
|
87
|
+
byte-identical app behavior, lifespan, graceful SIGTERM/SIGINT shutdown,
|
|
88
|
+
413 body cap, `FASTERAPI_POOL` worker tuning.
|
|
89
|
+
- Zero-copy file streaming on the Hyper host (`FileResponse` sendfile lane,
|
|
90
|
+
`Range` falls back to Starlette semantics).
|
|
91
|
+
- Direct dispatch: with no user middleware, Hyper skips Starlette's
|
|
92
|
+
per-request stack (ServerError/Exception wrappers, router loop).
|
|
93
|
+
- `get_openapi()` with FastAPI-identical output (operationIds, typed params,
|
|
94
|
+
`$ref` bodies, nested components, 422 contract, `servers`, securitySchemes,
|
|
95
|
+
per-op `security`), byte-identical Swagger/ReDoc/oauth2-redirect pages.
|
|
96
|
+
- Auth surface: OAuth2 (password, auth-code), OpenID Connect, HTTP
|
|
97
|
+
Basic/Bearer/Digest, API-key header/query/cookie, `Security` + scopes,
|
|
98
|
+
password-grant forms (permissive + strict).
|
|
99
|
+
- `BackgroundTasks` injection (shared per-request instance, FastAPI-exact
|
|
100
|
+
teardown-after-background ordering via send-inside-stack lifecycle).
|
|
101
|
+
- SSE streaming (`EventSourceResponse`, `ServerSentEvent`, byte-identical frames).
|
|
102
|
+
- `UploadFile`, `status`, `BackgroundTasks`, `Security`, `jsonable_encoder`,
|
|
103
|
+
`ORJSONResponse`/`UJSONResponse` shims, `middleware.*`, `staticfiles`,
|
|
104
|
+
`templating`, `logger`, `types`, `sse`, `utils` (`generate_unique_id`),
|
|
105
|
+
`param_functions`, `concurrency`, `dependencies`, `exception_handlers` modules.
|
|
106
|
+
- Response-model inference from return annotations (explicit
|
|
107
|
+
`response_model=None` disables), filter flags (`include`/`exclude`/
|
|
108
|
+
`exclude_unset`/`exclude_none`/…), `deprecated` + `operationId` support.
|
|
109
|
+
- Bare `dict`/`list[Item]` JSON bodies; `validate_json`/`dump_json`
|
|
110
|
+
single-pass paths; lazy body parsing; Rust scalar validators.
|
|
111
|
+
- `fasterapi` console script (`fasterapi run [--reload] [--hyper]`).
|
|
112
|
+
- Export-parity guard test, thread-soak tests, Hyper differential tests
|
|
113
|
+
(file bytes, 413, middleware fallback, graceful lifespan).
|
|
114
|
+
- Auto-generated README parity/bench tables (`scripts/update_readme_tables.py`,
|
|
115
|
+
refreshed every commit); local bench history; free-threaded check script.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
- Form field constraints dropped before validation.
|
|
119
|
+
- `self` solved as a query param in class dependencies.
|
|
120
|
+
- Instance (non-function) async dependencies never awaited.
|
|
121
|
+
- `id()`-keyed caches colliding after GC (weak keys).
|
|
122
|
+
- Lifespan shutdown unreachable (loop closed by `asyncio.run`); persistent
|
|
123
|
+
lifespan loop.
|
|
124
|
+
- Python SIGINT disposition racing tokio shutdown (neutralized during serve).
|
|
125
|
+
- `x-fasterapi-*` plan headers leaking to clients; `server` re-stamped.
|
|
126
|
+
- `Vec<u8>` crossing the boundary as `list[int]` instead of `bytes`.
|
|
127
|
+
|
|
128
|
+
### Performance
|
|
129
|
+
- Model validation x2.2 (`validate_json`), model dump x2.9 (`dump_json`),
|
|
130
|
+
router match x3.3 (`matchit`), JSON dumps x1.1 (direct writer).
|
|
131
|
+
- Hyper GET fan-out up to x1.37 with far better p90/p99; validation-heavy
|
|
132
|
+
POST trails on GIL builds (documented GIL physics).
|
|
133
|
+
|
|
134
|
+
### Known limitations
|
|
135
|
+
- Hyper host: HTTP/1.1 only, buffered bodies (16 MiB cap), concatenated
|
|
136
|
+
streaming, no mid-response disconnect polling, no WebSockets yet.
|
|
137
|
+
- `fastapi.cli`, `fastapi.utils` v1-compat internals, `dependencies.*`
|
|
138
|
+
internals, `openapi.webhooks` are not ported (documented in migration guide).
|
|
139
|
+
- Single-run loopback RPS swings ±15%; gates use a strict micro 3x plus a
|
|
140
|
+
wide macro band.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributing to fasterapi
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bash scripts/quickstart.sh # venv + deps + hooks + tests
|
|
7
|
+
# or: uv venv .venv && pip install -e ".[test,bench]" && maturin develop --release
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`pip install -e` builds the Rust extension via maturin. Pure-Python works
|
|
11
|
+
without it (`fasterapi.HAS_RUST` tells you which path is live).
|
|
12
|
+
|
|
13
|
+
## The one rule: parity is king
|
|
14
|
+
|
|
15
|
+
fasterapi's contract is **byte-identical FastAPI behavior**. Every change
|
|
16
|
+
must keep the differential suite green:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pytest tests/ -q # full suite (differential vs real FastAPI)
|
|
20
|
+
pytest tests/test_parity_extended.py -q # edge cases: DI, mounts, ordering, errors
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Writing a fix? Add a **differential test first**: build the same app with
|
|
24
|
+
`fastapi.FastAPI()` and `fasterapi.FastAPI()`, assert identical wire output.
|
|
25
|
+
Regression tests live in `tests/test_parity*.py`.
|
|
26
|
+
|
|
27
|
+
## Performance claims need numbers
|
|
28
|
+
|
|
29
|
+
- Micro fast-paths: `python benchmarks/benchmark_differential.py --micro-only`
|
|
30
|
+
- Full loopback: `python benchmarks/benchmark_differential.py --quick`
|
|
31
|
+
- Measure end-to-end, not just primitives — a fast `matchit` lookup means
|
|
32
|
+
nothing if dispatch still routes through Python (that gap is now closed
|
|
33
|
+
via `FastRouter`; keep it that way).
|
|
34
|
+
|
|
35
|
+
README tables are auto-generated — `scripts/update_readme_tables.py` runs
|
|
36
|
+
on every commit via `.githooks/pre-commit`. Don't edit `PARITY:`/`BENCH:`
|
|
37
|
+
blocks by hand.
|
|
38
|
+
|
|
39
|
+
## Hooks
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bash scripts/install_hooks.sh
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `pre-commit`: cargo fmt (advisory) + ruff (advisory) + README refresh + fast tests
|
|
46
|
+
- `pre-push`: full parity suite (reject on failure) + perf gates
|
|
47
|
+
|
|
48
|
+
## Areas with deliberate trade-offs (know before changing)
|
|
49
|
+
|
|
50
|
+
- `FastRouter` falls back to Starlette's linear scan for anything it can't
|
|
51
|
+
prove identical (ambiguous pattern orderings, Mount/Host, convertor
|
|
52
|
+
patterns, method mismatches). Correctness beats speed here.
|
|
53
|
+
- `_solve_inner` fans out only *yield-free* dep subtrees — yield deps stay
|
|
54
|
+
sequential because their stack-entry order determines teardown order.
|
|
55
|
+
- `fast_json_loads` delegates to stdlib: measured slower at every payload
|
|
56
|
+
size (PyObject materialization dominates). The Rust JSON writer is the
|
|
57
|
+
fast path and is used in response serialization.
|
|
58
|
+
- The Hyper host is experimental; `serve()` defaults to uvicorn. See
|
|
59
|
+
`docs/migration.md` for the known-differences table.
|
|
60
|
+
|
|
61
|
+
## Commit style
|
|
62
|
+
|
|
63
|
+
Conventional commits (`feat:`, `fix:`, `perf:`, `chore:`, `docs:`).
|
|
64
|
+
Parity bugs: `fix:` + differential regression test. Perf changes:
|
|
65
|
+
`perf:` + bench numbers in the commit message or README table refresh.
|