Search code examples
pythonpython-3.xpython-asynciosemanticsaiohttp

What happens when __aenter__() fails using 'async with'?


The PEP 492 mentions that:

async with EXPR as VAR:
    BLOCK

is semantically equivalent to:

mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__

VAR = await aenter(mgr)
try:
    BLOCK
except:
    if not await aexit(mgr, *sys.exc_info()):
        raise
else:
    await aexit(mgr, None, None, None)

However, VAR = await aenter(mgr) is not in the try block so I am wondering if __aenter__() is allowed to fail.

For example, in this this aiohttp snippet (taken from Getting Started):

import aiohttp
import asyncio

async def main():

    async with aiohttp.ClientSession() as session:
        async with session.get('http://python.org') as response:

            print("Status:", response.status)
            print("Content-type:", response.headers['content-type'])

            html = await response.text()
            print("Body:", html[:15], "...")

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

session.get('http://python.org') could fail and __aexit__() would not be called to close the context.


Solution

  • If __aenter__ fails, __aexit__ is indeed not run. Any required cleanup is __aenter__'s responsibility in this case.

    __aenter__ has more information about how far it got and what did or did not get successfully initialized, so having __aenter__ handle this is more convenient than expecting __aexit__ to handle cleaning up arbitrary partially-entered context manager states.

    (This is exactly the same for normal, non-async context managers.)