PythiloLesson 11 of 12 · progress kept in this browser

Async context managers

Opening a network connection takes waiting, and so does closing it. async with is with for resources whose setup and cleanup need await. An object supports it by defining async def __aenter__ and async def __aexit__.

The short form mirrors contextmanager:

import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def connection(name):
    await asyncio.sleep(0.01)  # connect
    print("open", name)
    try:
        yield name.upper()
    finally:
        await asyncio.sleep(0.01)  # close
        print("closed", name)

async def main():
    async with connection("db") as conn:
        print("using", conn)

asyncio.run(main())

Code before yield is the setup, and the yielded value is what as receives. An exception in the block, cancellation included, is raised at the yield, so a try around it decides which cleanup always happens.

Your task

Write an async context manager transaction(db) that awaits db.begin() on entry and gives the block db. When the block finishes, await db.commit(); when it raises or is cancelled, await db.rollback() instead and let the exception continue.