Django supports async views since version 3.1, so it's great for non-blocking calls to e.g. external HTTP APIs (using, for example, aiohttp).
I often see the following code sample, which I think is conceptually wrong (although it works perfectly fine):
import aiohttp
from django.http import HttpRequest, HttpResponse
async def view_bad_example1(request: HttpRequest):
async with aiohttp.ClientSession() as session:
async with session.get("https://example.com/") as example_response:
response_text = await example_response.text()
return HttpResponse(response_text[:42], content_type="text/plain")
This code creates a ClientSession for each incoming request, which is inefficient. aiohttp cannot then use e.g. connection pooling.
Don’t create a session per request. Most likely you need a session per application which performs all requests altogether.
Source: https://docs.aiohttp.org/en/stable/client_quickstart.html#make-a-request
The same applies to httpx:
On the other hand, a Client instance uses HTTP connection pooling. This means that when you make several requests to the same host, the Client will reuse the underlying TCP connection, instead of recreating one for every single request.
Source: https://www.python-httpx.org/advanced/#why-use-a-client
Is there any way to globally instantiate aiohttp.ClientSession in Django so that this instance can be shared across multiple requests? Don't forget that ClientSession must be created in a running eventloop (Why is creating a ClientSession outside of an event loop dangerous?), so we can't instantiate it e.g. in Django settings or as a module-level variable.
The closest I got is this code. However, I think this code is ugly and doesn't address e.g. closing the session.
CLIENT_SESSSION = None
async def view_bad_example2(request: HttpRequest):
global CLIENT_SESSSION
if not CLIENT_SESSSION:
CLIENT_SESSSION = aiohttp.ClientSession()
example_response = await CLIENT_SESSSION.get("https://example.com/")
response_text = await example_response.text()
return HttpResponse(response_text[:42], content_type="text/plain")
Basically I'm looking for the equivalent of Events from FastAPI that can be used to create/close some resource in an async context.
By the way here is a performance comparison using k6 between the two views:
view_bad_example1:avg=1.32s min=900.86ms med=1.14s max=2.22s p(90)=2s p(95)=2.1sview_bad_example2:avg=930.82ms min=528.28ms med=814.31ms max=1.66s p(90)=1.41s p(95)=1.52s