skip to Main Content

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search