def test_context_inner_function_reassigning_deleted_value_on_deletion_of_reassignemnt_should_not_see_outer_value( ): context_keys = set() ctx = ContextLocal() @ctx def testcall(): ctx.var1 = 2 assert ctx.var1 == 2 del ctx.var1 with pytest.raises(AttributeError): ctx.var1 ctx.var1 = 3 assert ctx.var1 == 3 del ctx.var1 # Previously deleted value should remain "deleted" with pytest.raises(AttributeError): ctx.var1 ctx.var1 = 1 testcall() # Value deleted in inner context must be available here assert ctx.var1 == 1
def test_context_isolates_async_loop(): ctx = ContextLocal() ctx.aa = 1 results = [] @ctx async def aiter(): ctx.aa = 3 assert ctx.aa == 3 print(ctx.aa) assert ctx.aa == 3 ctx.aa += 1 assert ctx.aa == 4 yield 2 assert ctx.aa == 4 @ctx async def entry(): ctx.aa = 2 counter = 0 async for i in aiter(): assert ctx.aa == 2 + counter ctx.aa += 10 counter += 10 asyncio.run(entry()) assert ctx.aa == 1
def test_context_local_in_with_block_dont_mixup_with_other_context(): ctx1 = ContextLocal() ctx2 = ContextLocal() ctx1.value = 1 ctx2.value = 2 with ctx1: ctx1.value = 3 with ctx2: assert ctx1.value == 3 ctx2.value = 4 assert ctx1.value == 3 assert ctx2.value == 4 with ctx1: ctx1.value = 5 assert ctx1.value == 5 assert ctx2.value == 4 assert ctx2.value == 4 assert ctx1.value == 3 assert ctx1.value == 3 assert ctx2.value == 2 assert ctx1.value == 1 assert ctx2.value == 2
def test_context_local_enter_new_context_in_with_block(): ctx = ContextLocal() ctx.value = 1 with ctx: ctx.value = 2 assert ctx.value == 2 assert ctx.value == 1
def test_context_local_in_with_block_can_see_outside_values(): ctx = ContextLocal() ctx.value = 1 with ctx: assert ctx.value == 1 ctx.value = 2 assert ctx.value == 2 assert ctx.value == 1
def test_context_run_method_isolates_context(): context_keys = set() ctx = ContextLocal() def testcall(): assert ctx.var1 == 1 ctx.var1 = 2 assert ctx.var1 == 2 del ctx.var1 ctx.var1 = 1 assert ctx.var1 == 1 ctx._run(testcall) assert ctx.var1 == 1
def test_context_local_async_reflect_changes_made_downstream(): ctx = ContextLocal() results = set() @ctx async def worker(value): ctx.value = value results.add(ctx.value) await second_stage_worker() assert ctx.value == value + 1 async def second_stage_worker(): await asyncio.sleep((10 - ctx.value) * 0.01) ctx.value += 1 results.add(ctx.value) @ctx def manager(): ctx.value = -1 tasks = asyncio.gather(*(worker(i) for i in range(0, 10, 2))) loop = asyncio.get_event_loop() loop.run_until_complete(tasks) assert all(i in results for i in range(10)) assert ctx.value == -1 manager()
def test_context_local_vars_work_for_async(): ctx = ContextLocal() results = set() @ctx async def worker(value): ctx.value = value await asyncio.sleep((10 - value) * 0.01) assert value == ctx.value results.add(ctx.value) @ctx def manager(): ctx.value = -1 tasks = asyncio.gather(*(worker(i) for i in range(10))) loop = asyncio.get_event_loop() loop.run_until_complete(tasks) assert all(i in results for i in range(10)) assert ctx.value == -1 manager()
def test_context_local_vars_work_for_threads(): ctx = ContextLocal() results = set() @ctx def worker(value): ctx.value = value time.sleep((10 - value) * 0.01) assert value == ctx.value results.add(ctx.value) @ctx def manager(): ctx.value = -1 tasks = [ threading.Thread(target=worker, args=(i, )) for i in range(10) ] consume(t.start() for t in tasks) consume(t.join() for t in tasks) assert all(i in results for i in range(10)) assert ctx.value == -1 manager()
def test_context_in_with_block_deleted_var_must_be_undeleted_outside(): ctx = ContextLocal() ctx.value = 1 with ctx: ctx.value = 2 assert ctx.value == 2 del ctx.value assert ctx.value == 1 del ctx.value with pytest.raises(AttributeError): assert ctx.value assert ctx.value == 1
def test_context_local_doesnt_leak_from_generator(): ctx = ContextLocal() @ctx def gen(): ctx.value = 2 yield assert ctx.value == 2 ctx.value = 1 g = gen() assert ctx.value == 1 next(g) assert ctx.value == 1 next(g, None) assert ctx.value == 1
def test_contexts_keep_separate_variables(): c1 = ContextLocal() c2 = ContextLocal() @c1 @c2 def inner(): c1.a = 1 c2.a = 2 assert c1.a == 1 assert c2.a == 2 del c2.a with pytest.raises(AttributeError): c2.a assert c1.a == 1 inner()
def test_context_dir(): ctx = ContextLocal() @ctx def testcall(): ctx.var2 = 2 assert "var1" in dir(ctx) assert "var2" in dir(ctx) del ctx.var1 ctx.var1 = 1 assert "var1" in dir(ctx) testcall() assert "var1" in dir(ctx) assert "var2" not in dir(ctx)
def test_context_inner_function_deleting_attribute_can_reassign_it(): context_keys = set() ctx = ContextLocal() @ctx def testcall(): ctx.var1 = 2 assert ctx.var1 == 2 del ctx.var1 with pytest.raises(AttributeError): ctx.var1 ctx.var1 = 3 assert ctx.var1 == 3 ctx.var1 = 1 testcall() # Value deleted in inner context must be available here assert ctx.var1 == 1
def test_context_local_vars_work_as_decorator(): ctx = ContextLocal() @ctx def func(): ctx.value = 1 assert ctx.value == 1 func() with pytest.raises(AttributeError): assert ctx.value == 1
def test_each_call_creates_unique_context_and_clean_up(): context_keys = set() ctx = ContextLocal() @ctx def testcall(): context_keys.update(ctx._registry.keys()) for i in range(10): testcall() assert len(context_keys) == 10 assert len(list(ctx._registry.keys())) == 0
def test_context_granddaugher_works_nice_with_daughter_deleting_attribute(): context_keys = set() ctx = ContextLocal() @ctx def granddaughter(): with pytest.raises(AttributeError): ctx.var1 ctx.var1 = 2 assert ctx.var1 == 2 @ctx def daughter(): assert ctx.var1 == 1 del ctx.var1 granddaughter() with pytest.raises(AttributeError): ctx.var1 ctx.var1 = 1 daughter() assert ctx.var1 == 1
def test_unique_context_for_generators_is_cleaned_up(): context_keys = set() ctx = ContextLocal() @ctx def testcall(): context_keys.update(k.value for k in ctx._registry.keys()) yield None for i in range(100): for _ in testcall(): pass gc.collect() assert len(context_keys) == 100 assert len(list(ctx._registry.keys())) == 0
def test_context_local_vars_work_for_generators(): ctx = ContextLocal() results = [] @contextmanager def use_mode(mode): previous = ctx.mode ctx.mode = mode try: yield finally: ctx.mode = previous @ctx def first(): ctx.mode = 0 results.append(("starting", ctx.mode)) with use_mode(1): results.append(("entered first", ctx.mode)) it = second() next(it) results.append(("back in first", ctx.mode)) next(it, None) results.append(("ended second", ctx.mode)) results.append(("exited first context manager", ctx.mode)) @ctx def second(): with use_mode(2): results.append(("entered second", ctx.mode)) yield results.append(("back in second", ctx.mode)) results.append(("exited second context manager", ctx.mode)) first() assert results == [('starting', 0), ('entered first', 1), ('entered second', 2), ('back in first', 1), ('back in second', 2), ('exited second context manager', 1), ('ended second', 1), ('exited first context manager', 0)]
def test_context_local_enter_new_context_in_nested_with_blocks(): ctx = ContextLocal() ctx.value = 1 with ctx: ctx.value = 2 with ctx: ctx.value = 3 with ctx: ctx.value = 4 assert ctx.value == 4 assert ctx.value == 3 assert ctx.value == 2 assert ctx.value == 1
https://stackoverflow.com/questions/53611690/how-do-i-write-consistent-stateful-context-managers/57448146 """ # Code demonstrating how generators # entered in different calls can each # have a separate context from contextlib import contextmanager #from contextvars import ContextVar, Context, copy_context from extracontext import ContextLocal ctx = ContextLocal() @contextmanager def use_mode(mode): ctx.MODE = mode print("entering use_mode") print_mode() try: yield finally: pass def print_mode():