Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use an aiohttp ClientSession with Sanic?

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?

like image 828
Tomer Avatar asked Aug 01 '18 16:08

Tomer


2 Answers

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.aiohttp_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.
like image 168
John Moutafis Avatar answered Sep 29 '22 22:09

John Moutafis


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.

like image 25
Adam Hopkins Avatar answered Sep 29 '22 22:09

Adam Hopkins