From ec85469befc17d035f90f275f263e39218bd30b9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Jul 2026 16:55:37 +0530 Subject: [PATCH 1/6] feat: implement route caching for static paths in APIRouter to improve routing performance --- fastapi/routing.py | 70 ++++++++++++++++++++++++++++++++--- tests/test_route_caching.py | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 tests/test_route_caching.py diff --git a/fastapi/routing.py b/fastapi/routing.py index e41ef6a59..7b65fef73 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2384,9 +2384,14 @@ class APIRouter(routing.Router): self._routes_version = 0 self._low_priority_routes: list[BaseRoute] = [] self._frontend_routes: _FrontendRouteGroup | None = None + self._route_cache: dict[ + tuple[str, str | None, str], + tuple[Match, BaseRoute, dict[str, Any], Any | None], + ] = {} def _mark_routes_changed(self) -> None: self._routes_version += 1 + self._route_cache.clear() def _get_routes_version(self, seen: set[int] | None = None) -> int: if seen is None: @@ -2535,20 +2540,73 @@ class APIRouter(routing.Router): await self.lifespan(scope, receive, send) return - partial: tuple[BaseRoute, Scope] | None = None + # Check cache + cache_key = (scope["type"], scope.get("method"), scope["path"]) + cached = self._route_cache.get(cache_key) + if cached is not None: + match_type, leaf_route, child_scope, effective_context = cached + if match_type in (Match.FULL, Match.PARTIAL): + resolved_scope = dict(child_scope) + if "path_params" in resolved_scope: + resolved_scope["path_params"] = dict(resolved_scope["path_params"]) + scope.update(resolved_scope) + if effective_context is not None: + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + scope["route"] = effective_context.original_route + else: + scope["route"] = leaf_route + await leaf_route.handle(scope, receive, send) + return + + partial: tuple[BaseRoute, Scope, BaseRoute, Any] | None = None for route in self.routes: - match, child_scope = route.matches(scope) + if isinstance(route, _IncludedRouter): + match, child_scope, leaf_route, effective_context = route._match(scope) + else: + match, child_scope = route.matches(scope) + leaf_route = route + effective_context = None + if match == Match.FULL: + is_static = False + if isinstance(leaf_route, APIRoute): + is_static = len(leaf_route.dependant.path_params) == 0 + elif isinstance(leaf_route, routing.Route): + is_static = not leaf_route.param_convertors + + if is_static: + self._route_cache[cache_key] = (Match.FULL, leaf_route, child_scope, effective_context) + scope.update(child_scope) - await route.handle(scope, receive, send) + if effective_context is not None: + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + scope["route"] = effective_context.original_route + else: + scope["route"] = leaf_route + await leaf_route.handle(scope, receive, send) return + if match == Match.PARTIAL and partial is None: - partial = (route, child_scope) + partial = (route, child_scope, leaf_route, effective_context) if partial is not None: - route, child_scope = partial + _, child_scope, leaf_route, effective_context = partial + is_static = False + if isinstance(leaf_route, APIRoute): + is_static = len(leaf_route.dependant.path_params) == 0 + elif isinstance(leaf_route, routing.Route): + is_static = not leaf_route.param_convertors + + if is_static: + self._route_cache[cache_key] = (Match.PARTIAL, leaf_route, child_scope, effective_context) + scope.update(child_scope) - await route.handle(scope, receive, send) + if effective_context is not None: + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + scope["route"] = effective_context.original_route + else: + scope["route"] = leaf_route + await leaf_route.handle(scope, receive, send) return route_path = get_route_path(scope) diff --git a/tests/test_route_caching.py b/tests/test_route_caching.py new file mode 100644 index 000000000..6b7a76093 --- /dev/null +++ b/tests/test_route_caching.py @@ -0,0 +1,74 @@ +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + + +def test_static_route_caching(): + app = FastAPI() + router = APIRouter() + + @router.get("/static") + def read_static(): + return {"status": "ok"} + + @router.get("/users/{user_id}") + def read_user(user_id: int): + return {"user_id": user_id} + + app.include_router(router) + client = TestClient(app) + + # First request: Cache miss, resolves normally + response = client.get("/static") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + # Check cache size (should be 1) + cache = app.router._route_cache + assert len(cache) == 1 + cache_key = ("http", "GET", "/static") + assert cache_key in cache + + # Second request: Cache hit, resolves via cache + response = client.get("/static") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + assert len(cache) == 1 + + # Dynamic request: matches but should not be cached + response = client.get("/users/123") + assert response.status_code == 200 + assert response.json() == {"user_id": 123} + assert cache_key in cache + # Verify dynamic path is not added to the cache + assert len(cache) == 1 + assert ("http", "GET", "/users/123") not in cache + + +def test_cache_invalidation(): + app = FastAPI() + router = APIRouter() + + @router.get("/health") + def health(): + return {"status": "healthy"} + + app.include_router(router) + client = TestClient(app) + + # First request + response = client.get("/health") + assert response.status_code == 200 + assert len(app.router._route_cache) == 1 + + # Invalidate by adding a new route dynamically + @app.get("/new-route") + def new_route(): + return {"new": "yes"} + + # Cache should be cleared + assert len(app.router._route_cache) == 0 + + # Request new route and health route to populate cache again + client.get("/new-route") + client.get("/health") + assert len(app.router._route_cache) == 2 From bdd4f265551a762f444a6f25c6579c670d543e01 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:27:21 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 7b65fef73..fab6ec3cb 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2551,7 +2551,9 @@ class APIRouter(routing.Router): resolved_scope["path_params"] = dict(resolved_scope["path_params"]) scope.update(resolved_scope) if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( + effective_context + ) scope["route"] = effective_context.original_route else: scope["route"] = leaf_route @@ -2575,11 +2577,18 @@ class APIRouter(routing.Router): is_static = not leaf_route.param_convertors if is_static: - self._route_cache[cache_key] = (Match.FULL, leaf_route, child_scope, effective_context) + self._route_cache[cache_key] = ( + Match.FULL, + leaf_route, + child_scope, + effective_context, + ) scope.update(child_scope) if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( + effective_context + ) scope["route"] = effective_context.original_route else: scope["route"] = leaf_route @@ -2598,11 +2607,18 @@ class APIRouter(routing.Router): is_static = not leaf_route.param_convertors if is_static: - self._route_cache[cache_key] = (Match.PARTIAL, leaf_route, child_scope, effective_context) + self._route_cache[cache_key] = ( + Match.PARTIAL, + leaf_route, + child_scope, + effective_context, + ) scope.update(child_scope) if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( + effective_context + ) scope["route"] = effective_context.original_route else: scope["route"] = leaf_route From 9c87bb09249dd40a9718f8281e19a2e8f87192b9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Jul 2026 17:09:31 +0530 Subject: [PATCH 3/6] perf: optimize route matching by caching static routes and adding a detection mechanism for custom routing classes --- benchmark_routing.py | 94 +++++++++++++++++++++++++++++++++++ fastapi/routing.py | 114 ++++++++++++++++++++++++++++++++----------- 2 files changed, 179 insertions(+), 29 deletions(-) create mode 100644 benchmark_routing.py diff --git a/benchmark_routing.py b/benchmark_routing.py new file mode 100644 index 000000000..fbfdf178e --- /dev/null +++ b/benchmark_routing.py @@ -0,0 +1,94 @@ +import time +from fastapi import APIRouter, FastAPI + +# Setup Router +app = FastAPI() +router = APIRouter() + +# 50 Static Routes +for i in range(50): + @router.get(f"/static-{i}") + def static_route(i=i): + return {"route": f"static-{i}"} + +# 50 Dynamic Routes +for i in range(50): + @router.get(f"/dynamic-{i}/{{item_id}}") + def dynamic_route(item_id: int, i=i): + return {"route": f"dynamic-{i}", "item_id": item_id} + +app.include_router(router) +import anyio +from contextlib import AsyncExitStack + +# Mock ASGI stack +async def mock_receive(): + return {"type": "http.request", "body": b"", "more_body": False} + +async def mock_send(message): + pass + +scope_static = { + "type": "http", + "method": "GET", + "path": "/static-49", + "headers": [], + "query_string": b"", + "client": ("127.0.0.1", 1234), + "server": ("127.0.0.1", 80), + "fastapi_middleware_astack": AsyncExitStack(), + "fastapi_inner_astack": AsyncExitStack(), + "fastapi_function_astack": AsyncExitStack(), +} + +scope_dynamic = { + "type": "http", + "method": "GET", + "path": "/dynamic-49/12345", + "headers": [], + "query_string": b"", + "client": ("127.0.0.1", 1234), + "server": ("127.0.0.1", 80), + "fastapi_middleware_astack": AsyncExitStack(), + "fastapi_inner_astack": AsyncExitStack(), + "fastapi_function_astack": AsyncExitStack(), +} + +async def run_benchmark(): + # Warmup + for _ in range(100): + await app.router.app(dict(scope_static), mock_receive, mock_send) + await app.router.app(dict(scope_dynamic), mock_receive, mock_send) + + print("Running Static Route Benchmark (10,000 iterations)...") + + # 1. Caching Enabled (standard flow) + # Warmup cache + await app.router.app(dict(scope_static), mock_receive, mock_send) + start_time = time.perf_counter() + for _ in range(10000): + await app.router.app(dict(scope_static), mock_receive, mock_send) + cache_enabled_time = time.perf_counter() - start_time + + # 2. Caching Disabled (clear cache before each request) + start_time = time.perf_counter() + for _ in range(10000): + app.router._route_cache.clear() + await app.router.app(dict(scope_static), mock_receive, mock_send) + cache_disabled_time = time.perf_counter() - start_time + + print(f"Static Route - Cache Enabled: {cache_enabled_time:.4f} seconds") + print(f"Static Route - Cache Disabled: {cache_disabled_time:.4f} seconds") + print(f"Speedup: {cache_disabled_time / cache_enabled_time:.2f}x") + + print("\nRunning Dynamic Route Benchmark (10,000 iterations)...") + + # Caching has no effect on dynamic routes + start_time = time.perf_counter() + for _ in range(10000): + await app.router.app(dict(scope_dynamic), mock_receive, mock_send) + dynamic_time = time.perf_counter() - start_time + print(f"Dynamic Route time: {dynamic_time:.4f} seconds") + +if __name__ == "__main__": + anyio.run(run_benchmark) diff --git a/fastapi/routing.py b/fastapi/routing.py index fab6ec3cb..38f9fd117 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2388,10 +2388,20 @@ class APIRouter(routing.Router): tuple[str, str | None, str], tuple[Match, BaseRoute, dict[str, Any], Any | None], ] = {} + self._custom_routing_detected: bool | None = None def _mark_routes_changed(self) -> None: self._routes_version += 1 self._route_cache.clear() + self._custom_routing_detected = None + + def _check_custom_routing(self) -> bool: + if type(self).matches is not APIRouter.matches or type(self).handle is not APIRouter.handle: + return True + for route in self.routes: + if isinstance(route, _IncludedRouter) and route.original_router._check_custom_routing(): + return True + return False def _get_routes_version(self, seen: set[int] | None = None) -> int: if seen is None: @@ -2540,36 +2550,61 @@ class APIRouter(routing.Router): await self.lifespan(scope, receive, send) return - # Check cache - cache_key = (scope["type"], scope.get("method"), scope["path"]) - cached = self._route_cache.get(cache_key) - if cached is not None: - match_type, leaf_route, child_scope, effective_context = cached - if match_type in (Match.FULL, Match.PARTIAL): - resolved_scope = dict(child_scope) - if "path_params" in resolved_scope: - resolved_scope["path_params"] = dict(resolved_scope["path_params"]) - scope.update(resolved_scope) - if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( - effective_context - ) - scope["route"] = effective_context.original_route + if self._custom_routing_detected is None: + self._custom_routing_detected = self._check_custom_routing() + + if not self._custom_routing_detected: + # Check cache + cache_key = (scope["type"], scope.get("method"), scope["path"]) + cached = self._route_cache.get(cache_key) + if cached is not None: + match_type, leaf_route, child_scope, effective_context = cached + if match_type in (Match.FULL, Match.PARTIAL): + resolved_scope = dict(child_scope) + if "path_params" in resolved_scope: + resolved_scope["path_params"] = dict(resolved_scope["path_params"]) + scope.update(resolved_scope) + if effective_context is not None: + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + scope["route"] = effective_context.original_route + else: + scope["route"] = leaf_route + await leaf_route.handle(scope, receive, send) + return + + partial_optimized: tuple[BaseRoute, Scope, BaseRoute, Any] | None = None + for route in self.routes: + if isinstance(route, _IncludedRouter): + match, child_scope, leaf_route, effective_context = route._match(scope) else: - scope["route"] = leaf_route - await leaf_route.handle(scope, receive, send) - return + match, child_scope = route.matches(scope) + leaf_route = route + effective_context = None + + if match == Match.FULL: + is_static = False + if isinstance(leaf_route, APIRoute): + is_static = len(leaf_route.dependant.path_params) == 0 + elif isinstance(leaf_route, routing.Route): + is_static = not leaf_route.param_convertors + + if is_static: + self._route_cache[cache_key] = (Match.FULL, leaf_route, child_scope, effective_context) + + scope.update(child_scope) + if effective_context is not None: + _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + scope["route"] = effective_context.original_route + else: + scope["route"] = leaf_route + await leaf_route.handle(scope, receive, send) + return - partial: tuple[BaseRoute, Scope, BaseRoute, Any] | None = None - for route in self.routes: - if isinstance(route, _IncludedRouter): - match, child_scope, leaf_route, effective_context = route._match(scope) - else: - match, child_scope = route.matches(scope) - leaf_route = route - effective_context = None + if match == Match.PARTIAL and partial_optimized is None: + partial_optimized = (route, child_scope, leaf_route, effective_context) - if match == Match.FULL: + if partial_optimized is not None: + _, child_scope, leaf_route, effective_context = partial_optimized is_static = False if isinstance(leaf_route, APIRoute): is_static = len(leaf_route.dependant.path_params) == 0 @@ -2577,12 +2612,16 @@ class APIRouter(routing.Router): is_static = not leaf_route.param_convertors if is_static: +<<<<<<< HEAD self._route_cache[cache_key] = ( Match.FULL, leaf_route, child_scope, effective_context, ) +======= + self._route_cache[cache_key] = (Match.PARTIAL, leaf_route, child_scope, effective_context) +>>>>>>> fb6c05704 (perf: optimize route matching by caching static routes and adding a detection mechanism for custom routing classes) scope.update(child_scope) if effective_context is not None: @@ -2595,9 +2634,19 @@ class APIRouter(routing.Router): await leaf_route.handle(scope, receive, send) return - if match == Match.PARTIAL and partial is None: - partial = (route, child_scope, leaf_route, effective_context) + else: + # Fallback to standard Starlette matching loop to preserve custom router matches/handle subclasses + partial: tuple[BaseRoute, Scope] | None = None + for route in self.routes: + match, child_scope = route.matches(scope) + if match == Match.FULL: + scope.update(child_scope) + await route.handle(scope, receive, send) + return + if match == Match.PARTIAL and partial is None: + partial = (route, child_scope) +<<<<<<< HEAD if partial is not None: _, child_scope, leaf_route, effective_context = partial is_static = False @@ -2624,6 +2673,13 @@ class APIRouter(routing.Router): scope["route"] = leaf_route await leaf_route.handle(scope, receive, send) return +======= + if partial is not None: + route, child_scope = partial + scope.update(child_scope) + await route.handle(scope, receive, send) + return +>>>>>>> fb6c05704 (perf: optimize route matching by caching static routes and adding a detection mechanism for custom routing classes) route_path = get_route_path(scope) if scope["type"] == "http" and self.redirect_slashes and route_path != "/": From aaac1a725d869b870bb9b155b6adf763efa3d15b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:43:51 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmark_routing.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/benchmark_routing.py b/benchmark_routing.py index fbfdf178e..724c8b430 100644 --- a/benchmark_routing.py +++ b/benchmark_routing.py @@ -1,4 +1,5 @@ import time + from fastapi import APIRouter, FastAPI # Setup Router @@ -7,27 +8,35 @@ router = APIRouter() # 50 Static Routes for i in range(50): + @router.get(f"/static-{i}") def static_route(i=i): return {"route": f"static-{i}"} + # 50 Dynamic Routes for i in range(50): + @router.get(f"/dynamic-{i}/{{item_id}}") def dynamic_route(item_id: int, i=i): return {"route": f"dynamic-{i}", "item_id": item_id} + app.include_router(router) -import anyio from contextlib import AsyncExitStack +import anyio + + # Mock ASGI stack async def mock_receive(): return {"type": "http.request", "body": b"", "more_body": False} + async def mock_send(message): pass + scope_static = { "type": "http", "method": "GET", @@ -54,6 +63,7 @@ scope_dynamic = { "fastapi_function_astack": AsyncExitStack(), } + async def run_benchmark(): # Warmup for _ in range(100): @@ -82,7 +92,7 @@ async def run_benchmark(): print(f"Speedup: {cache_disabled_time / cache_enabled_time:.2f}x") print("\nRunning Dynamic Route Benchmark (10,000 iterations)...") - + # Caching has no effect on dynamic routes start_time = time.perf_counter() for _ in range(10000): @@ -90,5 +100,6 @@ async def run_benchmark(): dynamic_time = time.perf_counter() - start_time print(f"Dynamic Route time: {dynamic_time:.4f} seconds") + if __name__ == "__main__": anyio.run(run_benchmark) From 77a64c99a7f9d3207860d5789805c6eb94969573 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Jul 2026 17:18:51 +0530 Subject: [PATCH 5/6] refactor: simplify partial route matching logic --- fastapi/routing.py | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 38f9fd117..31cbfc5b6 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2612,16 +2612,12 @@ class APIRouter(routing.Router): is_static = not leaf_route.param_convertors if is_static: -<<<<<<< HEAD self._route_cache[cache_key] = ( - Match.FULL, + Match.PARTIAL, leaf_route, child_scope, effective_context, ) -======= - self._route_cache[cache_key] = (Match.PARTIAL, leaf_route, child_scope, effective_context) ->>>>>>> fb6c05704 (perf: optimize route matching by caching static routes and adding a detection mechanism for custom routing classes) scope.update(child_scope) if effective_context is not None: @@ -2646,40 +2642,11 @@ class APIRouter(routing.Router): if match == Match.PARTIAL and partial is None: partial = (route, child_scope) -<<<<<<< HEAD - if partial is not None: - _, child_scope, leaf_route, effective_context = partial - is_static = False - if isinstance(leaf_route, APIRoute): - is_static = len(leaf_route.dependant.path_params) == 0 - elif isinstance(leaf_route, routing.Route): - is_static = not leaf_route.param_convertors - - if is_static: - self._route_cache[cache_key] = ( - Match.PARTIAL, - leaf_route, - child_scope, - effective_context, - ) - - scope.update(child_scope) - if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( - effective_context - ) - scope["route"] = effective_context.original_route - else: - scope["route"] = leaf_route - await leaf_route.handle(scope, receive, send) - return -======= if partial is not None: route, child_scope = partial scope.update(child_scope) await route.handle(scope, receive, send) return ->>>>>>> fb6c05704 (perf: optimize route matching by caching static routes and adding a detection mechanism for custom routing classes) route_path = get_route_path(scope) if scope["type"] == "http" and self.redirect_slashes and route_path != "/": From f21247ead8a832f43ff71188dfe83a8619d4b36e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:50:09 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 31cbfc5b6..4bc083714 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2396,10 +2396,16 @@ class APIRouter(routing.Router): self._custom_routing_detected = None def _check_custom_routing(self) -> bool: - if type(self).matches is not APIRouter.matches or type(self).handle is not APIRouter.handle: + if ( + type(self).matches is not APIRouter.matches + or type(self).handle is not APIRouter.handle + ): return True for route in self.routes: - if isinstance(route, _IncludedRouter) and route.original_router._check_custom_routing(): + if ( + isinstance(route, _IncludedRouter) + and route.original_router._check_custom_routing() + ): return True return False @@ -2562,10 +2568,14 @@ class APIRouter(routing.Router): if match_type in (Match.FULL, Match.PARTIAL): resolved_scope = dict(child_scope) if "path_params" in resolved_scope: - resolved_scope["path_params"] = dict(resolved_scope["path_params"]) + resolved_scope["path_params"] = dict( + resolved_scope["path_params"] + ) scope.update(resolved_scope) if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + _get_fastapi_scope(scope)[ + _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY + ] = effective_context scope["route"] = effective_context.original_route else: scope["route"] = leaf_route @@ -2575,7 +2585,9 @@ class APIRouter(routing.Router): partial_optimized: tuple[BaseRoute, Scope, BaseRoute, Any] | None = None for route in self.routes: if isinstance(route, _IncludedRouter): - match, child_scope, leaf_route, effective_context = route._match(scope) + match, child_scope, leaf_route, effective_context = route._match( + scope + ) else: match, child_scope = route.matches(scope) leaf_route = route @@ -2589,11 +2601,18 @@ class APIRouter(routing.Router): is_static = not leaf_route.param_convertors if is_static: - self._route_cache[cache_key] = (Match.FULL, leaf_route, child_scope, effective_context) + self._route_cache[cache_key] = ( + Match.FULL, + leaf_route, + child_scope, + effective_context, + ) scope.update(child_scope) if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = effective_context + _get_fastapi_scope(scope)[ + _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY + ] = effective_context scope["route"] = effective_context.original_route else: scope["route"] = leaf_route @@ -2601,7 +2620,12 @@ class APIRouter(routing.Router): return if match == Match.PARTIAL and partial_optimized is None: - partial_optimized = (route, child_scope, leaf_route, effective_context) + partial_optimized = ( + route, + child_scope, + leaf_route, + effective_context, + ) if partial_optimized is not None: _, child_scope, leaf_route, effective_context = partial_optimized