pythonasync-awaitpython-asynciocoroutinepython-contextvars

How to run a coroutine inside a context?


In the Python docs about Context Vars a Context::run method is described to enable executing a callable inside a context so changes that the callable perform to the context are contained inside the copied Context. Though what if you need to execute a coroutine? What are you supposed to do in order to achieve the same behavior?

In my case, what I wanted was something like this to handle a transactional context with possible nested transactions:

my_ctxvar = ContextVar("my_ctxvar")

async def coro(func, transaction):
    token = my_ctxvar.set(transaction)
    r = await func()
    my_ctxvar.reset(token)  # no real need for this, but why not either
    return r

async def foo():
    ctx = copy_context()
    # simplification to one case here: let's use the current transaction if there is one
    if tx_owner := my_ctxvar not in ctx:
        tx = await create_transaction()
    else:
        tx = my_ctxvar.get()
    
    try:
        r = await ctx.run(coro)  # not actually possible
        if tx_owner:
            await tx.commit()
    except Exception as e:
        if tx_owner:
            await tx.rollback()
        raise from e
    return r

Solution

  • As I already pointed out here, context variables are natively supported by asyncio and are ready to be used without any extra configuration. It should be noted that:

    Therefore, in order to execute a coroutine in a copy of the current context, you can execute it as a task:

    await asyncio.create_task(coro())
    

    Small example:

    import asyncio
    from contextvars import ContextVar
    
    var = ContextVar('var')
    
    
    async def foo():
        await asyncio.sleep(1)
        print(f"var inside foo {var.get()}")
        var.set("ham")  # change copy
    
    
    async def main():
        var.set('spam')
        await asyncio.create_task(foo())
        print(f"var after foo {var.get()}")
    
    
    asyncio.run(main())
    
    var inside foo spam
    var after foo spam