Inherit Starlette router dispatch - #16236
Conversation
Mukller
left a comment
There was a problem hiding this comment.
Verified locally on the PR branch (Python 3.13, editable install of the branch):
Behavioral parity probe (branch vs release 0.141.1) — four contracts, identical results:
| case | branch | release |
|---|---|---|
GET /path/ with only /path registered |
307 → http://testserver/path |
same |
GET /unknown |
404 {"detail": "Not Found"} |
same |
POST on GET-only path |
405 + allow: GET |
same |
/openapi.json |
200 | same |
Targeted test files: test_router_redirect_slashes.py, test_router_prefix_with_template.py, test_empty_router.py, test_router_events.py, test_include_router_defaults_overrides.py, test_ws_router.py — 70/70 green.
Checked the wiring details:
- Starlette's
Router.appsetsscope["router"] = selfjust like the removed fastapi copy did — no loss for middleware/introspection that reads it. - Match ordering is preserved: starlette's loop handles FULL/PARTIAL + its own redirect_slashes pass, then calls
self.default— which now points at_handle_no_match, so fastapi's low-priority route logic runs at the same point in the sequence as before, and_defaultkeeps the original 404 handler. - Deleting ~40 lines of drifted copy in favor of upstream's dispatch means future starlette routing fixes flow through automatically instead of needing manual sync — clearly the right direction.
One non-blocking nit: self._default = self.default; self.default = self._handle_no_match captures the original default at __init__ time, so a subclass that overrides .default after super().__init__() would have its override bypassed (_handle_no_match calls the captured _default). Extremely unlikely to matter in practice, but worth a comment line if you want to guard it.
CI fully green. Approving.
Summary
Remove FastAPI's copy of
Router.app()and express low-priority frontend routing through Starlette's existingdefaultcallback.Starlette remains responsible for lifespan handling, normal route matching, partial matches, and slash redirects. FastAPI handles low-priority routes only after Starlette finds no regular route or redirect, then delegates to the original default application.
This works with FastAPI's minimum supported Starlette version, 0.46.0. Once Starlette PR #3339 is available, FastAPI inherits its trie-backed candidate selection without another routing change.
Benchmark
The benchmark compares FastAPI
masterwith this PR while both use the same Starlette trie branch at3bfc970. It dispatches requests directly through the public FastAPI ASGI application. Results are median microseconds per request across four processes, with 2,000 requests per round and seven rounds per process. Lower is better.The first route remains approximately 13 us. The improvement grows with route count for late matches, misses, and
405 Method Not Allowedresponses. These gains apply when FastAPI is used with the trie-backed Starlette change.Tests
scripts/test.sh -q- 3348 passed, 17 skipped, 5 xfailedscripts/lint.shtests/test_frontend.pyagainst Starlette 0.46.0 - 93 passedAI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.