EDIT: As pointed out by Thierry Lathuille, PEP567, where ContextVar was introduced, was not designed to address generators (unlike the withdrawn PEP550). Still, the main question remains. How do I write stateful context managers that behave correctly with multiple threads, generators and asyncio tasks?
I have a library with some functions that can work in different "modes", so their behavior can be altered by a local context. I am looking at the contextvars module to implement this reliably, so I can use it from different threads, asynchronous contexts, etc. However, I am having trouble getting a simple example working right. Consider this minimal setup:
from contextlib import contextmanager
from contextvars import ContextVar
MODE = ContextVar('mode', default=0)
@contextmanager
def use_mode(mode):
    t = MODE.set(mode)
    try:
        yield
    finally:
        MODE.reset(t)
def print_mode():
   print(f'Mode {MODE.get()}')
Here is a small test with a generator function:
def first():
    print('Start first')
    print_mode()
    with use_mode(1):
        print('In first: with use_mode(1)')
        print('In first: start second')
        it = second()
        next(it)
        print('In first: back from second')
        print_mode()
        print('In first: continue second')
        next(it, None)
        print('In first: finish')
def second():
    print('Start second')
    print_mode()
    with use_mode(2):
        print('In second: with use_mode(2)')
        print('In second: yield')
        yield
        print('In second: continue')
        print_mode()
        print('In second: finish')
first()
I get the following output:
Start first
Mode 0
In first: with use_mode(1)
In first: start second
Start second
Mode 1
In second: with use_mode(2)
In second: yield
In first: back from second
Mode 2
In first: continue second
In second: continue
Mode 2
In second: finish
In first: finish
In the section:
In first: back from second
Mode 2
In first: continue second
It should be Mode 1 instead of Mode 2, because this was printed from first, where the applying context should be, as I understand it, use_mode(1). However, it seems that the use_mode(2) of second is stacked over it until the generator finishes. Are generators not supported by contextvars? If so, is there any way to support stateful context managers reliably? By reliably, I mean it should behave consistently whether I use:
- Multiple threads.
- Generators.
- asyncio
 
     
    