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.
Files changed (119) hide show
  1. fasterapi_rs-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  2. fasterapi_rs-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  3. fasterapi_rs-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  4. fasterapi_rs-0.1.0/.gitignore +37 -0
  5. fasterapi_rs-0.1.0/CHANGELOG.md +140 -0
  6. fasterapi_rs-0.1.0/CONTRIBUTING.md +65 -0
  7. fasterapi_rs-0.1.0/Cargo.lock +1102 -0
  8. fasterapi_rs-0.1.0/Cargo.toml +42 -0
  9. fasterapi_rs-0.1.0/LICENSE +21 -0
  10. fasterapi_rs-0.1.0/PKG-INFO +393 -0
  11. fasterapi_rs-0.1.0/README.md +347 -0
  12. fasterapi_rs-0.1.0/SECURITY.md +30 -0
  13. fasterapi_rs-0.1.0/benchmarks/apps.py +10 -0
  14. fasterapi_rs-0.1.0/benchmarks/benchmark_differential.py +468 -0
  15. fasterapi_rs-0.1.0/docs/assets/banner.svg +33 -0
  16. fasterapi_rs-0.1.0/docs/assets/logo.svg +26 -0
  17. fasterapi_rs-0.1.0/docs/hyper-gates.md +57 -0
  18. fasterapi_rs-0.1.0/docs/migration.md +93 -0
  19. fasterapi_rs-0.1.0/examples/comparison/AUDIT.md +112 -0
  20. fasterapi_rs-0.1.0/examples/comparison/README.md +63 -0
  21. fasterapi_rs-0.1.0/examples/comparison/STAGES.md +41 -0
  22. fasterapi_rs-0.1.0/examples/comparison/audit.py +635 -0
  23. fasterapi_rs-0.1.0/examples/comparison/fastapi_app.py +367 -0
  24. fasterapi_rs-0.1.0/examples/comparison/fasterapi_app.py +378 -0
  25. fasterapi_rs-0.1.0/examples/comparison/stack/__init__.py +12 -0
  26. fasterapi_rs-0.1.0/examples/comparison/stack/db.py +146 -0
  27. fasterapi_rs-0.1.0/examples/comparison/stack/schemas.py +41 -0
  28. fasterapi_rs-0.1.0/examples/comparison/stack/security.py +27 -0
  29. fasterapi_rs-0.1.0/examples/comparison/stack/settings.py +42 -0
  30. fasterapi_rs-0.1.0/examples/comparison/stack/stack.yaml +3 -0
  31. fasterapi_rs-0.1.0/examples/comparison/static/hello.txt +1 -0
  32. fasterapi_rs-0.1.0/examples/quickstart.py +34 -0
  33. fasterapi_rs-0.1.0/examples/taskapi/README.md +68 -0
  34. fasterapi_rs-0.1.0/examples/taskapi/app/__init__.py +0 -0
  35. fasterapi_rs-0.1.0/examples/taskapi/app/core/__init__.py +0 -0
  36. fasterapi_rs-0.1.0/examples/taskapi/app/core/config.py +35 -0
  37. fasterapi_rs-0.1.0/examples/taskapi/app/core/deps.py +40 -0
  38. fasterapi_rs-0.1.0/examples/taskapi/app/core/security.py +36 -0
  39. fasterapi_rs-0.1.0/examples/taskapi/app/db/__init__.py +0 -0
  40. fasterapi_rs-0.1.0/examples/taskapi/app/db/store.py +111 -0
  41. fasterapi_rs-0.1.0/examples/taskapi/app/main.py +58 -0
  42. fasterapi_rs-0.1.0/examples/taskapi/app/models/__init__.py +0 -0
  43. fasterapi_rs-0.1.0/examples/taskapi/app/models/schemas.py +67 -0
  44. fasterapi_rs-0.1.0/examples/taskapi/app/routers/__init__.py +0 -0
  45. fasterapi_rs-0.1.0/examples/taskapi/app/routers/auth.py +29 -0
  46. fasterapi_rs-0.1.0/examples/taskapi/app/routers/files.py +40 -0
  47. fasterapi_rs-0.1.0/examples/taskapi/app/routers/stream.py +28 -0
  48. fasterapi_rs-0.1.0/examples/taskapi/app/routers/tasks.py +68 -0
  49. fasterapi_rs-0.1.0/examples/taskapi/requirements.txt +5 -0
  50. fasterapi_rs-0.1.0/examples/taskapi/tests/__init__.py +0 -0
  51. fasterapi_rs-0.1.0/examples/taskapi/tests/test_api.py +92 -0
  52. fasterapi_rs-0.1.0/pyproject.toml +78 -0
  53. fasterapi_rs-0.1.0/python/fasterapi/__init__.py +99 -0
  54. fasterapi_rs-0.1.0/python/fasterapi/__main__.py +8 -0
  55. fasterapi_rs-0.1.0/python/fasterapi/_fast_router.py +155 -0
  56. fasterapi_rs-0.1.0/python/fasterapi/_hyper_serve.py +702 -0
  57. fasterapi_rs-0.1.0/python/fasterapi/_rust_bridge.py +368 -0
  58. fasterapi_rs-0.1.0/python/fasterapi/_version.py +3 -0
  59. fasterapi_rs-0.1.0/python/fasterapi/applications.py +386 -0
  60. fasterapi_rs-0.1.0/python/fasterapi/background.py +11 -0
  61. fasterapi_rs-0.1.0/python/fasterapi/cli.py +145 -0
  62. fasterapi_rs-0.1.0/python/fasterapi/concurrency.py +42 -0
  63. fasterapi_rs-0.1.0/python/fasterapi/dag.py +117 -0
  64. fasterapi_rs-0.1.0/python/fasterapi/datastructures.py +47 -0
  65. fasterapi_rs-0.1.0/python/fasterapi/dependencies.py +6 -0
  66. fasterapi_rs-0.1.0/python/fasterapi/encoders.py +49 -0
  67. fasterapi_rs-0.1.0/python/fasterapi/exception_handlers.py +37 -0
  68. fasterapi_rs-0.1.0/python/fasterapi/exceptions.py +135 -0
  69. fasterapi_rs-0.1.0/python/fasterapi/guards.py +95 -0
  70. fasterapi_rs-0.1.0/python/fasterapi/logger.py +7 -0
  71. fasterapi_rs-0.1.0/python/fasterapi/middleware/__init__.py +7 -0
  72. fasterapi_rs-0.1.0/python/fasterapi/middleware/asyncexitstack.py +35 -0
  73. fasterapi_rs-0.1.0/python/fasterapi/middleware/cors.py +5 -0
  74. fasterapi_rs-0.1.0/python/fasterapi/middleware/gzip.py +5 -0
  75. fasterapi_rs-0.1.0/python/fasterapi/middleware/httpsredirect.py +5 -0
  76. fasterapi_rs-0.1.0/python/fasterapi/middleware/trustedhost.py +5 -0
  77. fasterapi_rs-0.1.0/python/fasterapi/middleware/wsgi.py +5 -0
  78. fasterapi_rs-0.1.0/python/fasterapi/openapi/__init__.py +7 -0
  79. fasterapi_rs-0.1.0/python/fasterapi/openapi/constants.py +7 -0
  80. fasterapi_rs-0.1.0/python/fasterapi/openapi/docs.py +213 -0
  81. fasterapi_rs-0.1.0/python/fasterapi/openapi/utils.py +454 -0
  82. fasterapi_rs-0.1.0/python/fasterapi/openapi/webhooks.py +8 -0
  83. fasterapi_rs-0.1.0/python/fasterapi/param_functions.py +19 -0
  84. fasterapi_rs-0.1.0/python/fasterapi/params.py +205 -0
  85. fasterapi_rs-0.1.0/python/fasterapi/py.typed +1 -0
  86. fasterapi_rs-0.1.0/python/fasterapi/requests.py +6 -0
  87. fasterapi_rs-0.1.0/python/fasterapi/responses.py +120 -0
  88. fasterapi_rs-0.1.0/python/fasterapi/routing.py +1715 -0
  89. fasterapi_rs-0.1.0/python/fasterapi/security/__init__.py +375 -0
  90. fasterapi_rs-0.1.0/python/fasterapi/security/api_key.py +12 -0
  91. fasterapi_rs-0.1.0/python/fasterapi/security/base.py +5 -0
  92. fasterapi_rs-0.1.0/python/fasterapi/security/http.py +18 -0
  93. fasterapi_rs-0.1.0/python/fasterapi/security/oauth2.py +16 -0
  94. fasterapi_rs-0.1.0/python/fasterapi/security/open_id_connect_url.py +9 -0
  95. fasterapi_rs-0.1.0/python/fasterapi/security/utils.py +5 -0
  96. fasterapi_rs-0.1.0/python/fasterapi/sse.py +126 -0
  97. fasterapi_rs-0.1.0/python/fasterapi/staticfiles.py +5 -0
  98. fasterapi_rs-0.1.0/python/fasterapi/status.py +5 -0
  99. fasterapi_rs-0.1.0/python/fasterapi/templating.py +5 -0
  100. fasterapi_rs-0.1.0/python/fasterapi/testclient.py +20 -0
  101. fasterapi_rs-0.1.0/python/fasterapi/types.py +10 -0
  102. fasterapi_rs-0.1.0/python/fasterapi/utils.py +118 -0
  103. fasterapi_rs-0.1.0/python/fasterapi/websockets.py +7 -0
  104. fasterapi_rs-0.1.0/rust-toolchain.toml +2 -0
  105. fasterapi_rs-0.1.0/scripts/free-threaded-check.sh +37 -0
  106. fasterapi_rs-0.1.0/scripts/install_hooks.sh +9 -0
  107. fasterapi_rs-0.1.0/scripts/quickstart.sh +14 -0
  108. fasterapi_rs-0.1.0/scripts/soak-hyper.sh +101 -0
  109. fasterapi_rs-0.1.0/scripts/update_readme_tables.py +276 -0
  110. fasterapi_rs-0.1.0/src/lib.rs +2223 -0
  111. fasterapi_rs-0.1.0/tests/__init__.py +0 -0
  112. fasterapi_rs-0.1.0/tests/test_exports.py +166 -0
  113. fasterapi_rs-0.1.0/tests/test_fasterapi_native.py +225 -0
  114. fasterapi_rs-0.1.0/tests/test_hyper_host.py +341 -0
  115. fasterapi_rs-0.1.0/tests/test_parity.py +729 -0
  116. fasterapi_rs-0.1.0/tests/test_parity_extended.py +471 -0
  117. fasterapi_rs-0.1.0/tests/test_parity_security.py +314 -0
  118. fasterapi_rs-0.1.0/tests/test_parity_surface.py +170 -0
  119. 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.