using slowapi with a datamodel instead of request as parameter - python

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

Related

How to get information from post data using fastapi

My script looks like this
from pydantic import BaseModel
from fastapi import FastAPI
class Credentials(BaseModel):
url:str
username:str
password:str
#app.post("/"/credentials"")
async def post_credentials(conn: Required):
return conn
#app.get("/main")
async def receive_from_post():
I would like to know if there is a way to retrieve the data from "POST" in "GET"

How to write a custom FastAPI middleware class

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)

What is fastApi analog for the flask request.environ.get('HTTP_REFERER', "")?

So I want to get current request 'HTTP_REFERER'. In Flask it is in request.environ.get('HTTP_REFERER', ""). How to get one in fastapi?
HTTP_REFERER is just a request header, which you can access in a FastAPI endpoint as follows:
from fastapi import FastAPI, Request
app = FastAPI()
#app.get("/foo")
def foo(request: Request):
http_referer = request.headers.get('HTTP_REFERER')
return {"http_referer": http_referer}
More information located in the FastAPI docs.

How to add multiple body params with fileupload in FastAPI?

I have a machine learning model deployed using FastAPI, but the issue is I need the model to take two-body parameters
app = FastAPI()
class Inputs(BaseModel):
industry: str = None
file: UploadFile = File(...)
#app.post("/predict")
async def predict(inputs: Inputs):
# params
industry = inputs.industry
file = inputs.file
### some code ###
return predicted value
When I tried to send the input parameters I am getting an error in postman, please see the pic below,
From the FastAPI discussion thread--(#657)
if you are receiving JSON data, with application/json, use normal Pydantic models.
This would be the most common way to communicate with an API.
If you are receiving a raw file, e.g. a picture or PDF file to store it in the server, then use UploadFile, it will be sent as form data (multipart/form-data).
If you need to receive some type of structured content that is not JSON but you want to validate in some way, for example, an Excel file, you would still have to upload it using UploadFile and do all the necessary validations in your code. You could use Pydantic in your own code for your validations, but there's no way for FastAPI to do it for you in that case.
So, in your case, the router should be as,
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
#app.post("/predict")
async def predict(
industry: str = Form(...),
file: UploadFile = File(...)
):
# rest of your logic
return {"industry": industry, "filename": file.filename}

FastAPI equivalent of Flask's request.form, for agnostic forms

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"}

Categories

Resources