How to use async function based views in DRF? - python

Since Django now supports async views, I'm trying to change my code base which contains a lot of function based views to be async but for some reason its not working.
#api_view(["GET"])
async def test_async_view(request):
...
data = await get_data()
return Response(data)
When I send a request to this endpoint, I get an error saying:
AssertionError: Expected a Response, HttpResponse or
HttpStreamingResponse to be returned from the view, but received a
<class 'coroutine'>
Does DRF not support async views yet? Is there an alternative I can do to get this working?

As of now, DRF doesn't support async "api views". Here is an open issue (#7260) in the DRF community and it is still in the discussion stage.
But, Django providing a decorator/wrapper which allow us to convert our sync views/function to async using sync_to_async(...) wrapper.
Example,
#sync_to_async
#api_view(["GET"])
def sample_view(request):
data = get_data()
return Response(data)
Note that, here, sample_view(...) and get_data(...) are sync functions.

I think you can Use this decorator in DRF
import asyncio
from functools import wraps
def to_async(blocking):
#wraps(blocking)
def run_wrapper(*args, **kwargs):
return asyncio.run(blocking(*args, **kwargs))
return run_wrapper
Example of usage
#to_async
#api_view(["GET"])
async def sample_view(request):
...

Related

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)

Reading json:api filters with fast api

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

A Coroutine is returning a coroutine after await

I'm writing tests for a fastAPI on django with an ASGI server (adapted this tutorial). My fastAPI side of the test keeps returning errors and I'm trying in vain to fix it.
My need is about creating a user to test the API.
#sync_to_async
def _create_user(self, username, email):
try:
return User.objects.create(username=username, email=email)
except:
return None
async def setUp(self):
task = asyncio.create_task(self._create_user(username="user", email="email#email.com"))
self.user = await task
Running this test, it turn out that self.user is a coroutine and it's impossible to access the attributes I expect.
How to solve this ?
Update :
Removed async for _create_user(self, username, email).
According to the docs https://docs.djangoproject.com/en/3.1/topics/async/#asgiref.sync.sync_to_async
decorator sync_to_async should decorates sync functions. (see example)
I have an answer,.......................
Simply Create a normal synchronize function and call with with sync_to_async() function
This function is used to get the user_object
def get_user_object(mobile_no):
user_object = CustomUser.objects.get(mobile_no=mobile_no)
return user_object
This method is running in async so we need to call a sync function to async to get rid of error......
from channels.db import database_sync_to_async
async def connect(self):
username = await database_sync_to_async(get_user_object)(mobile_no="+9999999999")
print(user_object)

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

How can I do custom JWT validation with Flask and flask_jwt_extended?

I want to add additional verification to the token when #jwt_required is called. I want to verify one of the claims. Is there away I can do this with JWTManager?
Currently my code just calls:
jwt = JWTManager(app)
And I decorate the functions with: #jwt_required
Off the top of my head, my inclination would be to create a custom decorator that wraps jwt_required.
Here's a rough idea of how it might look, via the functools.wraps documentation:
from functools import wraps
from flask_jwt_extended import jwt_required
from flask_jwt_extended.view_decorators import _decode_jwt_from_request
from flask_jwt_extended.exceptions import NoAuthorizationError
def custom_validator(view_function):
#wraps(view_function)
def wrapper(*args, **kwargs):
jwt_data = _decode_jwt_from_request(request_type='access')
# Do your custom validation here.
if (...):
authorized = True
else:
authorized = False
if not authorized:
raise NoAuthorizationError("Explanation goes here")
return view_function(*args, **kwargs)
return jwt_required(wrapper)
#app.route('/')
#custom_validator
def index():
return render_template('index.html')
Here is where you can find the source code for jwt_required.
Posted this in your other question, but I'll post it here too just in case others stumble upon this.
Author here. For what it's worth, flask-jwt doesn't support requiring claims either (even though it says it does). https://github.com/mattupstate/flask-jwt/issues/98
EDIT: This is now available in flask-jwt-extended. https://github.com/vimalloc/flask-jwt-extended/issues/64#issuecomment-318800617
Cheers

Categories

Resources