I am trying to understand what is the right way to use aiohttp with Sanic.
From aiohttp documentation, I find the following:
Don’t create a session per request. Most likely you need a session per application which performs all requests altogether.
More complex cases may require a session per site, e.g. one for Github and another one for Facebook APIs. Anyway making a session for every request is a very bad idea.
A session contains a connection pool inside. Connection reuse and keep-alive (both are on by default) may speed up total performance.
And when I go to Sanic documentation I find an example like this:
This is an example:
from sanic import Sanic
from sanic.response import json
import asyncio
import aiohttp
app = Sanic(__name__)
sem = None
@app.route("/")
async def test(request):
"""
Download and serve example JSON
"""
url = "https://api.github.com/repos/channelcat/sanic"
async with aiohttp.ClientSession() as session:
async with sem, session.get(url) as response:
return await response.json()
app.run(host="0.0.0.0", port=8000, workers=2)
Which is not the right way to manage an aiohttp session...
So what is the right way?
Should I init a session in the app and inject the session to all the methods in all layers?
The only issue I found is this but this doesn't help because I need to make my own classes to use the session, and not sanic.
Also found this in Sanic documentation, which says you shouldn't create a session outside of an eventloop.
I am a little confused :(
What is the right way to go?
In order to use a single aiohttp.ClientSession
we need to instantiate the session only once and use that specific instance in the rest of the application.
To achieve this we can use a before_server_start
listener which will allow us to create the instance before the app serves the first byte.
from sanic import Sanic
from sanic.response import json
import aiohttp
app = Sanic(__name__)
@app.listener('before_server_start')
def init(app, loop):
app.aiohttp_session = aiohttp.ClientSession(loop=loop)
@app.listener('after_server_stop')
def finish(app, loop):
loop.run_until_complete(app.session.close())
loop.close()
@app.route("/")
async def test(request):
"""
Download and serve example JSON
"""
url = "https://api.github.com/repos/channelcat/sanic"
async with app.aiohttp_session.get(url) as response:
return await response.json()
app.run(host="0.0.0.0", port=8000, workers=2)
Breakdown of the code:
- We are creating an
aiohttp.ClientSession
, passing as argument the loop that Sanic
apps create at the start, avoiding this pitfall in the process.
- We store that session in the Sanic
app
.
- Finally, we are using this session to make our requests.
That is essentially what I am doing.
I created a module (interactions.py
) that has, for example a function like this:
async def get(url, headers=None, **kwargs):
async with aiohttp.ClientSession() as session:
log.debug(f'Fetching {url}')
async with session.get(url, headers=headers, ssl=ssl) as response:
try:
return await response.json()
except Exception as e:
log.error(f'Unable to complete interaction: {e}')
return await response.text()
Then I just await
on that:
results = await interactions.get(url)
I am not sure why that is not the "right way". The session (at least for my needs) can be closed as soon as my request is done.