Search code examples
pythonpython-asyncioaiohttp

aiohttp: when is the response.status available?


The Getting Started docs for aiohttp give the following client example:

async with aiohttp.ClientSession() as session:
    async with session.get('https://api.github.com/events') as resp:
        print(resp.status)
        print(await resp.text())

I'm having trouble understanding when the response.status will be available. My understanding is that the coroutines releases control at the await response.read() line. How can I possibly access status before waiting for the response to comeback?


Solution

  • Important distinction: await ... may release control of the context, for example if the awaited data is not avalible fast enough. The same goes for the async with ... statement. Therefore your code reaches the line print(resp.status) not until the resp is avalible.

    For example the code:

    import aiohttp
    import asyncio
    import urllib.parse
    import datetime
    
    async def get(session, url):
        print("[{:%M:%S.%f}] getting {} ...".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname))
        async with session.get(url) as resp:
            print("[{:%M:%S.%f}] {}, status: {}".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname, resp.status))
            doc = await resp.text()
            print("[{:%M:%S.%f}] {}, len: {}".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname, len(doc)))
    
    async def main():
        session = aiohttp.ClientSession()
    
        url = "http://demo.borland.com/Testsite/stadyn_largepagewithimages.html"
        f1 = asyncio.ensure_future(get(session, url))
        print("[{:%M:%S.%f}] added {} to event loop".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname))
    
        url = "https://stackoverflow.com/questions/46445019/aiohttp-when-is-the-response-status-available"
        f2 = asyncio.ensure_future(get(session, url))
        print("[{:%M:%S.%f}] added {} to event loop".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname))
    
        url = "https://api.github.com/events"
        f3 = asyncio.ensure_future(get(session, url))
        print("[{:%M:%S.%f}] added {} to event loop".format(datetime.datetime.now(), urllib.parse.urlsplit(url).hostname))
    
        await f1
        await f2
        await f3
    
        session.close()
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
    

    can produce this result:

    [16:42.415481] added demo.borland.com to event loop
    [16:42.415481] added stackoverflow.com to event loop
    [16:42.415481] added api.github.com to event loop
    [16:42.415481] getting demo.borland.com ...
    [16:42.422481] getting stackoverflow.com ...
    [16:42.682496] getting api.github.com ...
    [16:43.002515] demo.borland.com, status: 200
    [16:43.510544] stackoverflow.com, status: 200
    [16:43.759558] stackoverflow.com, len: 110650
    [16:43.883565] demo.borland.com, len: 239012
    [16:44.089577] api.github.com, status: 200
    [16:44.318590] api.github.com, len: 43055
    

    Clarification (thx @deceze): Here you can see (look at the times between the brackets) all coroutines releasing control after sending a request to retrieve the website and a second time while awaiting the text of the response. Also borland, in contrast to stackoverflow, has so much text (other network characteristics excluded) that it's only ready to be displayed after the text from stackoverflow was printed, despite being requested earlier.