From 71fb6b64c7d8d15f5271017c60b58687382b39c4 Mon Sep 17 00:00:00 2001 From: fatimah alhumaidhi Date: Sat, 19 Apr 2025 11:27:21 +0300 Subject: [PATCH 1/2] add support for starlette's scoped middleware --- docs/en/docs/advanced/middleware.md | 14 ++ docs_src/advanced_middleware/tutorial004.py | 61 +++++++ fastapi/applications.py | 132 +++++++++++++++ fastapi/routing.py | 160 ++++++++++++++++++ .../test_tutorial004.py | 55 ++++++ 5 files changed, 422 insertions(+) create mode 100644 docs_src/advanced_middleware/tutorial004.py create mode 100644 tests/test_tutorial/test_advanced_middleware/test_tutorial004.py diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md index 1d40b1c8f..792f8cfc7 100644 --- a/docs/en/docs/advanced/middleware.md +++ b/docs/en/docs/advanced/middleware.md @@ -84,6 +84,20 @@ The following arguments are supported: * `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`. * `compresslevel` - Used during GZip compression. It is an integer ranging from 1 to 9. Defaults to `9`. Lower value results in faster compression but larger file sizes, while higher value results in slower compression but smaller file sizes. +## Router and Route-Level Middleware Example + +**FastAPI supports adding scoped middleware per route and router. Middleware execution order:** + +- **App‑level middleware** runs on *every* request as soon as it enters your application, before any router or route is matched. +- **Router‑level middleware** runs next, wrapping all requests to routes included on that router. +- **Route‑level middleware** runs last, just around the specific path operation. + +This gives better control over where and when logic executes. + +The example below shows middleware applied at each scope. Notice how the inner route’s middleware is able to match path params ;) + +{* ../../docs_src/advanced_middleware/tutorial004.py *} + ## Other middlewares There are many other ASGI middlewares. diff --git a/docs_src/advanced_middleware/tutorial004.py b/docs_src/advanced_middleware/tutorial004.py new file mode 100644 index 000000000..bbe1e3572 --- /dev/null +++ b/docs_src/advanced_middleware/tutorial004.py @@ -0,0 +1,61 @@ +from fastapi import APIRouter, FastAPI, Request +from starlette.middleware import Middleware +from starlette.middleware.base import BaseHTTPMiddleware + +app = FastAPI() + + +@app.middleware("http") +async def app_middleware(request: Request, call_next): + print("App before") + response = await call_next(request) + print("App after") + return response + + +async def outer_middleware(request: Request, call_next): + print("Outer before") + response = await call_next(request) + print("Outer after") + return response + + +outer = APIRouter( + prefix="/outer", + middleware=[Middleware(BaseHTTPMiddleware, dispatch=outer_middleware)], +) + + +async def name_middleware(request: Request, call_next): + print(f"Hi {request.path_params.get('name')}!") + response = await call_next(request) + print(f"Bye {request.path_params.get('name')}!") + return response + + +inner = APIRouter(prefix="/inner") + + +@inner.get( + "/{name}", + middleware=[Middleware(BaseHTTPMiddleware, dispatch=name_middleware)], +) +async def hello(name: str): + print("Handler") + return {"message": f"Hello {name} from inner!"} + + +@outer.get("/") +async def outer_hello(): + print("Handler") + return {"message": "Hello from outer!"} + + +@app.get("/") +async def app_hello(): + print("Handler") + return {"message": "Hello from app!"} + + +outer.include_router(inner) +app.include_router(outer) diff --git a/fastapi/applications.py b/fastapi/applications.py index 6d427cdc2..2befee740 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1084,6 +1084,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), + middleware: Optional[List[Middleware]] = None, ) -> None: self.router.add_api_route( path, @@ -1110,6 +1111,7 @@ class FastAPI(Starlette): name=name, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def api_route( @@ -1140,6 +1142,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), + middleware: Optional[List[Middleware]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.router.add_api_route( @@ -1167,6 +1170,7 @@ class FastAPI(Starlette): name=name, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) return func @@ -1788,6 +1792,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP GET operation. @@ -1828,6 +1847,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def put( @@ -2161,6 +2181,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PUT operation. @@ -2206,6 +2241,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def post( @@ -2539,6 +2575,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP POST operation. @@ -2584,6 +2635,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def delete( @@ -2917,6 +2969,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP DELETE operation. @@ -2957,6 +3024,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def options( @@ -3290,6 +3358,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP OPTIONS operation. @@ -3330,6 +3413,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def head( @@ -3663,6 +3747,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP HEAD operation. @@ -3703,6 +3802,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def patch( @@ -4036,6 +4136,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PATCH operation. @@ -4081,6 +4196,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def trace( @@ -4414,6 +4530,21 @@ class FastAPI(Starlette): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP TRACE operation. @@ -4454,6 +4585,7 @@ class FastAPI(Starlette): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def websocket_route( diff --git a/fastapi/routing.py b/fastapi/routing.py index 457481e32..1bb7dc39d 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -60,6 +60,7 @@ from pydantic import BaseModel from starlette import routing from starlette.concurrency import run_in_threadpool from starlette.exceptions import HTTPException +from starlette.middleware import Middleware from starlette.requests import Request from starlette.responses import JSONResponse, Response from starlette.routing import ( @@ -459,6 +460,7 @@ class APIRoute(routing.Route): generate_unique_id_function: Union[ Callable[["APIRoute"], str], DefaultPlaceholder ] = Default(generate_unique_id), + middleware: Optional[Sequence[Middleware]] = None, ) -> None: self.path = path self.endpoint = endpoint @@ -566,8 +568,13 @@ class APIRoute(routing.Route): name=self.unique_id, embed_body_fields=self._embed_body_fields, ) + self.middleware = middleware self.app = request_response(self.get_route_handler()) + if middleware is not None: + for cls, args, kwargs in reversed(middleware): + self.app = cls(self.app, *args, **kwargs) + def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: return get_request_handler( dependant=self.dependant, @@ -833,6 +840,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this router. + + Router-level middleware is executed after application-level middleware. + When multiple routers declare middleware, the outermost (furthest) router's middleware runs first. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> None: super().__init__( routes=routes, @@ -841,6 +863,7 @@ class APIRouter(routing.Router): on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan, + middleware=middleware, ) if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" @@ -858,6 +881,7 @@ class APIRouter(routing.Router): self.route_class = route_class self.default_response_class = default_response_class self.generate_unique_id_function = generate_unique_id_function + self.middleware = middleware def route( self, @@ -911,6 +935,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Union[ Callable[[APIRoute], str], DefaultPlaceholder ] = Default(generate_unique_id), + middleware: Optional[List[Middleware]] = None, ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -930,6 +955,9 @@ class APIRouter(routing.Router): current_generate_unique_id = get_value_or_default( generate_unique_id_function, self.generate_unique_id_function ) + if middleware and self.middleware: + middleware = self.middleware + middleware + route = route_class( self.prefix + path, endpoint=endpoint, @@ -957,6 +985,7 @@ class APIRouter(routing.Router): callbacks=current_callbacks, openapi_extra=openapi_extra, generate_unique_id_function=current_generate_unique_id, + middleware=middleware or self.middleware, ) self.routes.append(route) @@ -989,6 +1018,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), + middleware: Optional[List[Middleware]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_api_route( @@ -1017,6 +1047,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) return func @@ -1328,6 +1359,7 @@ class APIRouter(routing.Router): callbacks=current_callbacks, openapi_extra=route.openapi_extra, generate_unique_id_function=current_generate_unique_id, + middleware=route.middleware, ) elif isinstance(route, routing.Route): methods = list(route.methods or []) @@ -1694,6 +1726,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP GET operation. @@ -1738,6 +1785,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def put( @@ -2071,6 +2119,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PUT operation. @@ -2120,6 +2183,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def post( @@ -2453,6 +2517,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP POST operation. @@ -2502,6 +2581,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def delete( @@ -2835,6 +2915,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP DELETE operation. @@ -2879,6 +2974,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def options( @@ -3212,6 +3308,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP OPTIONS operation. @@ -3256,6 +3367,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def head( @@ -3589,6 +3701,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP HEAD operation. @@ -3638,6 +3765,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def patch( @@ -3971,6 +4099,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP PATCH operation. @@ -4020,6 +4163,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) def trace( @@ -4353,6 +4497,21 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to apply to all requests handled by this route. + + Route-level middleware is executed after application-level and router-level middleware. + Any middleware declared on the router will be called before this route's middleware. + Middleware are applied in reverse order: the last middleware in this list is the first to be called. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/advanced/middleware/) + """ + ), + ] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: """ Add a *path operation* using an HTTP TRACE operation. @@ -4402,6 +4561,7 @@ class APIRouter(routing.Router): callbacks=callbacks, openapi_extra=openapi_extra, generate_unique_id_function=generate_unique_id_function, + middleware=middleware, ) @deprecated( diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial004.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial004.py new file mode 100644 index 000000000..c182c59d1 --- /dev/null +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial004.py @@ -0,0 +1,55 @@ +from fastapi.testclient import TestClient + +from docs_src.advanced_middleware.tutorial004 import app + +client = TestClient(app) + + +def test_app_middleware_called_once(capsys): + r = client.get("/") + assert r.status_code == 200 + + captured = capsys.readouterr().out + assert captured.count("App before") == 1 + assert captured.count("Outer before") == 0 + assert captured.count("Handler") == 1 + assert captured.count("Outer after") == 0 + assert captured.count("App after") == 1 + + +def test_outer_middleware_called_once(capsys): + r = client.get("/outer/") + assert r.status_code == 200 + + captured = capsys.readouterr().out + assert captured.count("App before") == 1 + assert captured.count("Outer before") == 1 + assert captured.count("Handler") == 1 + assert captured.count("Outer after") == 1 + assert captured.count("App after") == 1 + + +def test_name_middleware_called_once(capsys): + name = "you" + r = client.get(f"/outer/inner/{name}") + assert r.status_code == 200 + assert r.json() == {"message": f"Hello {name} from inner!"} + + captured = capsys.readouterr().out + seq = [ + "App before", + "Outer before", + f"Hi {name}!", + "Handler", + f"Bye {name}!", + "Outer after", + "App after", + ] + for msg in seq: + assert captured.count(msg) == 1 + + idx = 0 + for msg in seq: + next_idx = captured.find(msg, idx) + assert next_idx >= idx + idx = next_idx From cd7e29d85fc34a7685445410d7baefd9b61decd0 Mon Sep 17 00:00:00 2001 From: fatimah alhumaidhi Date: Sat, 19 Apr 2025 11:51:33 +0300 Subject: [PATCH 2/2] unify types --- fastapi/applications.py | 4 ++-- fastapi/routing.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 2befee740..1e0959b3a 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1084,7 +1084,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), - middleware: Optional[List[Middleware]] = None, + middleware: Optional[Sequence[Middleware]] = None, ) -> None: self.router.add_api_route( path, @@ -1142,7 +1142,7 @@ class FastAPI(Starlette): generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), - middleware: Optional[List[Middleware]] = None, + middleware: Optional[Sequence[Middleware]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.router.add_api_route( diff --git a/fastapi/routing.py b/fastapi/routing.py index 1bb7dc39d..65d0d99df 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -935,7 +935,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Union[ Callable[[APIRoute], str], DefaultPlaceholder ] = Default(generate_unique_id), - middleware: Optional[List[Middleware]] = None, + middleware: Optional[Sequence[Middleware]] = None, ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -956,7 +956,7 @@ class APIRouter(routing.Router): generate_unique_id_function, self.generate_unique_id_function ) if middleware and self.middleware: - middleware = self.middleware + middleware + middleware = list(self.middleware) + list(middleware) route = route_class( self.prefix + path, @@ -1018,7 +1018,7 @@ class APIRouter(routing.Router): generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), - middleware: Optional[List[Middleware]] = None, + middleware: Optional[Sequence[Middleware]] = None, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_api_route(