aiohttp.ClientSession -a proper way to use real-world apps

1,255 views
Skip to first unread message

hai...@gmail.com

unread,
Nov 9, 2018, 6:41:00 AM11/9/18
to aio-libs
Good day ladies and gents,

I would like to understand what's the "blessed" way to use aiohttp.ClientSession in real applications, particularly with regards to session life-cycle.
Most of the examples in docs create the session through context manager, and I wonder how does it map to a "real" code where you generally have a "starter" function that hooks up components together and lunches them.

E.g. if we take a simple server (not necessary aiohttp.web server) that talks to several upstream APIs:

import asyncio
import aiohttp

import settings  # My project settings lib


class UpstreamObjectsClient:
   
def __init__(self, host, path="/objects"):
       
self.host = host
       
self.path = path
       
self.session = aiohttp.ClientSession()
   
    async
def get(self, obj_id):
        url
= "http://{}{}/{}".format(self.host, self.path, obj_id)
        resp
= await self.session.get(url)
        resp
.raise_for_status()
       
return await resp.json()
   
    async
def close(self):
        await
self.session.close()
       

class MyService:
   
def __init__(self, upstream_objects_client):
       
self.uoc = upstream_objects_client

    async
def serve(self):
       
# Do something useful today
       
pass


async
def compose_and_run():
    uoc
= UpstreamObjectsClient(settings.host)
    svc
= MyService(uoc)
   
try:
        await svc
.serve()
   
finally:
        await uoc
.close()


if __name__ == "__main__":
    asyncio
.run(compose_and_run())  # Python 3.7



So, with regards to compose_and_run() function above - is this indeed a recommended approach? I mean I could use 

async with UpstreamObjectsClient(settings.host) as uoc:
   
MyService(uoc).serve()

But in a real application where I can have much more services, it can become something unpretty like:

async with Svc1() as svc1:
    async
with Svc2() as svc2:
        async
with Sbc3() as svc3:
           
MyServer(svc1, svc2, svc3).server()


Another option I saw is contextlib.AsyncExitStack (Python 3.7 only):

from contextlib import AsyncExitStack

async
def compose_and_run():
    async
with AsyncExitStack as stack:
        uoc
= UpstreamObjectsClient(settings.host)
        stack
.push_async_callback(uoc.close)  # Or just make UpstreamObjectsServer to work as context manager
        await
MyService(uoc).server()


So, what do you use? Try...finally? AsyncExitStack? Something else I didn't think of?

Thank you,
Zaar

Andrew Svetlov

unread,
Nov 9, 2018, 6:50:27 AM11/9/18
to aio-libs
try/finally from the first example looks pretty readable and straightforward.

If you prefer AsyncExitStack -- please feel free to use it.

Deep nested `async with` statements look ugly, agree -- but simple plain context manager is very useful for trivial cases and brief examples.

Gustavo Carneiro

unread,
Nov 9, 2018, 1:55:03 PM11/9/18
to aio-...@googlegroups.com
I agree, most of the time I use explicit `await session.close()` and not `async with`.

Moreover, I am annoyed by asyncio libs authors that provide APIs that work only as context managers and make it really hard for developers that want a simple open() / close() API.

Context managers are a terrible match for things that have lifecycle tied to large objects.  They are only good for things with short-lived contexts, inside a function.  But sometimes it's not efficient to continually create and close a resource,;you may want to create a resource at application startup and only destroy it at application shutdown, for example.

Reminds me of the Zen of Python:

   > Namespaces are one honking great idea -- let's do more of those!

Replace "Namespaces" with "Context managers".


--
You received this message because you are subscribed to the Google Groups "aio-libs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to aio-libs+u...@googlegroups.com.
To post to this group, send email to aio-...@googlegroups.com.
Visit this group at https://groups.google.com/group/aio-libs.
To view this discussion on the web visit https://groups.google.com/d/msgid/aio-libs/0d12cc6c-0750-463a-be2f-25f12118b911%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
Gustavo J. A. M. Carneiro
Gambit Research
"The universe is always one step beyond logic." -- Frank Herbert

Ray Cote

unread,
Nov 9, 2018, 3:28:36 PM11/9/18
to gjcar...@gmail.com, aio-...@googlegroups.com
Thanks for asking this. I'm also trying to understand the best practices for this. 

I understand the try/except example in the original email -- but what would a reasonable approach look like for two (or five, or more) connections maintained to back-end services. 

For example, I have a service that talks to web services on a half-dozen different domains. 
How do I best manage the multiple ClientSessions in that example? 
--Ray



For more options, visit https://groups.google.com/d/optout.


--
Raymond Cote, President
voice: +1.603.924.6079 email: rga...@AppropriateSolutions.com skype: ray.cote


Gustavo Carneiro

unread,
Nov 9, 2018, 4:36:09 PM11/9/18
to rga...@appropriatesolutions.com, aio-...@googlegroups.com
On Fri, 9 Nov 2018 at 20:28, Ray Cote <rga...@appropriatesolutions.com> wrote:
Thanks for asking this. I'm also trying to understand the best practices for this. 

I understand the try/except example in the original email -- but what would a reasonable approach look like for two (or five, or more) connections maintained to back-end services. 

For example, I have a service that talks to web services on a half-dozen different domains. 
How do I best manage the multiple ClientSessions in that example? 

As I understand it, an aiohttp ClientSession is like an http client connection pool.  It sounds like you should have a single ClientSession for all those domains.

Ray Cote

unread,
Nov 9, 2018, 9:56:07 PM11/9/18
to Gustavo Carneiro, aio-...@googlegroups.com
On Fri, Nov 9, 2018 at 4:36 PM Gustavo Carneiro <gjcar...@gmail.com> wrote:


On Fri, 9 Nov 2018 at 20:28, Ray Cote <rga...@appropriatesolutions.com> wrote:
Thanks for asking this. I'm also trying to understand the best practices for this. 

I understand the try/except example in the original email -- but what would a reasonable approach look like for two (or five, or more) connections maintained to back-end services. 

For example, I have a service that talks to web services on a half-dozen different domains. 
How do I best manage the multiple ClientSessions in that example? 

As I understand it, an aiohttp ClientSession is like an http client connection pool.  It sounds like you should have a single ClientSession for all those domains.

That was not my understanding (though I may me wrong). 
The documentation says:
                    The session contains a cookie storage and connection pool, thus cookies and connections are 
                     shared between HTTP requests sent by the same session.
And
                    If you need separate cookies for different http calls but process them in logical chains, 
                     use a single aiohttp.TCPConnector with separate client sessions and own_connector=False.

implied I should be using multiple ClientSessions
—Ray



For more options, visit https://groups.google.com/d/optout.

hai...@gmail.com

unread,
Nov 10, 2018, 2:52:36 AM11/10/18
to aio-libs
Hi Ray,

Please see below inline.


On Saturday, 10 November 2018 07:28:36 UTC+11, Ray Cote wrote:

I understand the try/except example in the original email -- but what would a reasonable approach look like for two (or five, or more) connections maintained to back-end services. 

You can just

async def compose_and_run():
   svc1
= Svc1()
   svc2
= Svc2()
   svc3
= Svc3()

   
try:
        await
Server(svc1, svc2, svc3).serve()
   
finally:
        await asyncio
.gather((svc1.close(), svc2.close(), svc3.close()))




     

Andrew Svetlov

unread,
Nov 10, 2018, 3:22:31 PM11/10/18
to aio-libs
HTTP based API usually never use cookies.

Therefore the single client session can be used for communicating with all servers simultaneously without any problem.

hai...@gmail.com

unread,
Nov 11, 2018, 5:49:25 AM11/11/18
to aio-libs
Generally I think we, as humans, naturally think of clean-up as something secondary at times (think cooking then washing dishes). Yes, as we mature professionally, it changes, but still, when I read documentation I spend more time understanding how to initialize something than on how to clean it up.

This is the point IMHO where context managers provide some relieve - by using them one only needs to know how to initialize and clean-up will be done for you. Combined with contextmanager.(Async)StackExit (which I yet had a chance to use in real code) we can now initialize several context manager without nesting hell; and if you follow "I gave you brith, I'll give you death" paradigm, it actually makes sense.

Sharing a ClientSession between multiple services looks like a boundary crossing to me - if a service (class) gives me an API to use, upper-level code does not necessary needs to know how the API transport works.

My 2c,
Zaar
Reply all
Reply to author
Forward
0 new messages