I have read FastAPI's documentation about middlewares (specifically, the middleware tutorial, the CORS middleware section and the advanced middleware guide), but couldn't find a concrete example of how to write a middleware class which you can add using the add_middleware function (in contrast to a basic middleware function added using a decorator) there nor on this site.
The reason I prefer to use add_middleware over the app based decorator, is that I want to write a middleware in a shared library that will be used by several different projects, and therefore I can't tie it to a specific FastAPI instance.
So my question is: how do you do it?
As FastAPI is actually Starlette underneath, you could use BaseHTTPMiddleware that allows you to implement a middleware class (you may want to have a look at this post as well). Below are given two variants of the same approach on how to do that, where the add_middleware() function is used to add the middleware class. Please note that is currently not possible to use BackgroundTasks (if that's a requirement for your task) with BaseHTTPMiddleware—check #1438 and #1640 for more details. Alternatives can be found in this answer and this answer.
Option 1
middleware.py
from fastapi import Request
class MyMiddleware:
def __init__(
self,
some_attribute: str,
):
self.some_attribute = some_attribute
async def __call__(self, request: Request, call_next):
# do something with the request object
content_type = request.headers.get('Content-Type')
print(content_type)
# process the request and get the response
response = await call_next(request)
return response
app.py
from fastapi import FastAPI
from middleware import MyMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
my_middleware = MyMiddleware(some_attribute="some_attribute_here_if_needed")
app.add_middleware(BaseHTTPMiddleware, dispatch=my_middleware)
Option 2
middleware.py
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
class MyMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app,
some_attribute: str,
):
super().__init__(app)
self.some_attribute = some_attribute
async def dispatch(self, request: Request, call_next):
# do something with the request object, for example
content_type = request.headers.get('Content-Type')
print(content_type)
# process the request and get the response
response = await call_next(request)
return response
app.py
from fastapi import FastAPI
from middleware import MyMiddleware
app = FastAPI()
app.add_middleware(MyMiddleware, some_attribute="some_attribute_here_if_needed")
A potential workaround for the BaseHTTPMiddleware bug raised by #Error - Syntactical Remorse, which seems to work for me at least, is to use partial and use a functional approach to your middleware definition:
middleware.py
from typing import Any, Callable, Coroutine
from fastapi import Response
async def my_middleware(request: Request, call_next: Callable, some_attribute: Any) -> Response:
request.state.attr = some_attribute # Do what you need with your attribute
return await call_next(request)
app.py
from functools import partial
from fastapi import FastAPI
from middleware import my_middleware
app = FastAPI()
my_custom_middleware: partial[Coroutine[Any, Any, Any]] = partial(my_middleware, some_attribute="my-app")
app.middleware("http")(my_custom_middlware)
Related
I need to create a session for authentication in the session_set endpoint. However, for some reason, the session is still being created in the session_info endpoint. How to make a session created only in session_set? Otherwise, I have a new session in the response with each request.
Here is my code:
import uvicorn
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string", max_age=None)
#app.get("/a")
async def session_set(request: Request):
request.session["my_var"] = "1234"
return 'ok'
#app.get("/b")
async def session_info(request: Request):
my_var = request.session.get("my_var", None)
return my_var
if __name__ == '__main__':
uvicorn.run('http-session:app', port=5000, reload=True)
You could use a Middleware to override the session value in the Response cookies (check the documentation in Starlette as well) every time a new request arrives; hence, the session will remain the same.
Note: Remember to declare your custom middleware, after adding the SessionMiddleware to the app instance, as the order that endpoints/sub-applications are defined in your application matters, as described in this answer (see the relevant FastAPI documentation as well).
Working Example:
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")
#app.middleware("http")
async def some_middleware(request: Request, call_next):
response = await call_next(request)
session = request.cookies.get('session')
if session:
response.set_cookie(key='session', value=request.cookies.get('session'), httponly=True)
return response
#app.get("/a")
def func_a(request: Request):
request.session["my_var"] = "1234"
print(request.cookies.get('session'))
return 'OK'
#app.get("/b")
def func_b(request: Request):
my_var = request.session.get("my_var", None)
print(request.cookies.get('session'))
return my_var
I want to use slowapi on a fastapi python app, for implementing RateLimiter. However, The definitions of the endpoints use a data model for parameters (inheriting from BaseModel) rather than a request, as the documentation of slowapi require. here is a sample endpoint from the code:
#app.post("/process_user")
def process_user(
params: NewUser,
pwd: str = Depends(authenticate),
):
where NewUser is the data model
class NewUser(BaseModel):
...
How can I add a slowapi rate limiter with minimum change of the design of the code?
Thanks.
You need to pass a Starlette request with your Pydantic model. For example...
from fastapi import FastAPI
from pydantic import BaseModel
from slowapi import Limiter
from slowapi.util import get_remote_address
from starlette.requests import Request
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
class NewUser(BaseModel):
...
#app.post("/process_user")
#limiter.limit("1/second")
def process_user(
params: NewUser,
pwd: str = Depends(authenticate),
request: Request
):
I'm trying to move my rest api project from flask to FastAPI
on my get function I made it very easy to get all the filters and by using flask I used the command
filter_value = request.args.get(f'filter[{filter_name}]')
where filter_name changed between all the fields i have on my object and that's how i could read urls like
http://127.0.0.1:8000/beers?filter[isbn]=72533
but now when I moved to fast api I can't find a way to read url like this.
Naming parameters in such a way, in my opinion, is not good.
Despite that, here's a working example.
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/")
async def get(req: Request):
if "filter[filter]" in req.query_params:
return req.query_params["filter[filter]"]
return ""
You have to directly access the request and manipulate it, since it is not possible to declare it as a function's parameter due to it's name. Choosing a different naming convention would allow to define the parameter as in the example below
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/")
async def get(my_parameter=None):
if my_parameter is not None:
return my_parameter
return ""
I try to migrate from Flask to FastAPI, and I was wondering if there is something similar to Flask's:
payload = request.form.to_dict(flat=False)
payload = {key:payload[key][0] for key in payload}
for FastAPI.
Until now I've found only some hacks, were you still had to implement one-by-one all the form's arguments to a function:
from pydantic import BaseModel
class FormData(BaseModel):
alfa: str=Form(...)
vita: str=Form(...)
async def Home(request: Request, form_data:FormData)
This example is of course better in readability than the standard form handling:
async def Home(username: str = Form(...), something_else: str = Form(...)):
But still it's quite restricting, due to the necessary declaration of all form fields.
Is there any other more agnostic & elegant approach?
Thanks in advance & I apologize if this a trivial question I've failed to find through googling :)
You can get the underlying starlette request and use its request.form() method. It requires python-multipart to work:
from fastapi import FastAPI, Request
app = FastAPI()
#app.post("/example")
async def example(request: Request):
form_data = await request.form()
return form_data
Example of calling it:
C:\>curl -X POST "http://localhost:8000/example" -d "hello=there&another=value"
{"hello":"there","another":"value"}
I,m getting this error when I try to run my FastApi api.
app = cls(app=app, **options)
TypeError: 'module' object is not callable
I'm trying to add a middleware on other folder separeted from main.py and don't know why isn't working. Otherwise when I add the middleware code into main.py works without problems. Here is my code, thank you for your help and excuse my english.
main.py
from fastapi import FastAPI
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Request
from routers import rutas
from utils import CheckApiKey
from utils.CheckApiKey import check_api_key
app = FastAPI()
app.add_middleware(CheckApiKey, dispatch=check_api_key) <--- Here calling middleware
app.include_router(rutas.router)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
Middleware
from fastapi import Request
async def check_api_key(request: Request, call_next):
print("ok")
response = await call_next(request)
return response
I found the solution...I have to create a class that inherite from starlette basehttpmiddleare, like this.
from starlette.middleware.base import BaseHTTPMiddleware
class CheckApiKey(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
print("ok")
response = await call_next(request)
return response
The CheckApiKey seems like a python module in your case and check_api_key is the middleware function.
The issue was, the add_middleware() method expects the first argument as a callable function or callable class. But in your case, you were given a module.
So,
Change your statement as,
app.add_middleware(check_api_key)