Browse Source

Merge fe09a698bf into 313723494b

pull/13627/merge
Fatimah Alhumaidhi 2 days ago
committed by GitHub
parent
commit
9e68e99317
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      docs/en/docs/advanced/middleware.md
  2. 61
      docs_src/advanced_middleware/tutorial004.py
  3. 132
      fastapi/applications.py
  4. 160
      fastapi/routing.py
  5. 55
      tests/test_tutorial/test_advanced_middleware/test_tutorial004.py

14
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.

61
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)

132
fastapi/applications.py

@ -1084,6 +1084,7 @@ class FastAPI(Starlette):
generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(
generate_unique_id
),
middleware: Optional[Sequence[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[Sequence[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(

160
fastapi/routing.py

@ -61,6 +61,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 (
@ -460,6 +461,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
@ -567,8 +569,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,
@ -834,6 +841,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,
@ -842,6 +864,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 '/'"
@ -859,6 +882,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,
@ -912,6 +936,7 @@ class APIRouter(routing.Router):
generate_unique_id_function: Union[
Callable[[APIRoute], str], DefaultPlaceholder
] = Default(generate_unique_id),
middleware: Optional[Sequence[Middleware]] = None,
) -> None:
route_class = route_class_override or self.route_class
responses = responses or {}
@ -931,6 +956,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 = list(self.middleware) + list(middleware)
route = route_class(
self.prefix + path,
endpoint=endpoint,
@ -958,6 +986,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)
@ -990,6 +1019,7 @@ class APIRouter(routing.Router):
generate_unique_id_function: Callable[[APIRoute], str] = Default(
generate_unique_id
),
middleware: Optional[Sequence[Middleware]] = None,
) -> Callable[[DecoratedCallable], DecoratedCallable]:
def decorator(func: DecoratedCallable) -> DecoratedCallable:
self.add_api_route(
@ -1018,6 +1048,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
return func
@ -1329,6 +1360,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 [])
@ -1695,6 +1727,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.
@ -1739,6 +1786,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def put(
@ -2072,6 +2120,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.
@ -2121,6 +2184,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def post(
@ -2454,6 +2518,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.
@ -2503,6 +2582,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def delete(
@ -2836,6 +2916,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.
@ -2880,6 +2975,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def options(
@ -3213,6 +3309,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.
@ -3257,6 +3368,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def head(
@ -3590,6 +3702,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.
@ -3639,6 +3766,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def patch(
@ -3972,6 +4100,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.
@ -4021,6 +4164,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
def trace(
@ -4354,6 +4498,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.
@ -4403,6 +4562,7 @@ class APIRouter(routing.Router):
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
middleware=middleware,
)
@deprecated(

55
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
Loading…
Cancel
Save