Re: ContextVars in async context

2022-12-21 Thread Dieter Maurer
Marce Coll wrote at 2022-12-20 22:09 +0100:
>Hi python people, hope this is the correct place to ask this!
>
>For a transactional async decorator I'm building I am using contextvars in 
>order to know when a transaction is open in my current context.
>
>My understanding is that if given the following call stack
>
>A
>|- B
>|  |- C
>|- D
>   |- E
>
>If you set a context var at A with value 1, and then override it at B with 
>value 2, then A, D and E will see value 1 and B and C will se value 2. Very 
>similar (although a bit more manual) than dynamic scopes in common lisp.

This is not the way I understand context variables.
In my view (--> PEP 0567), the context is the coroutine not the call stack.
This means: all calls in the same coroutine share the same context
variables. In your example, if `B` overrides the context variable, then
all later calls in this coroutine will see the overridden value.
-- 
https://mail.python.org/mailman/listinfo/python-list


ContextVars in async context

2022-12-20 Thread Marce Coll
Hi python people, hope this is the correct place to ask this!

For a transactional async decorator I'm building I am using contextvars in 
order to know when a transaction is open in my current context.

My understanding is that if given the following call stack

A
|- B
|  |- C
|- D
   |- E

If you set a context var at A with value 1, and then override it at B with 
value 2, then A, D and E will see value 1 and B and C will se value 2. Very 
similar (although a bit more manual) than dynamic scopes in common lisp.

Now, back to the transactional decorator, from the little documentation there 
is about this I would imagine that something like this:

@asynccontextmanager
async def transactional(
endpoint: _base.Endpoint,
isolation_level: IsolationLevel = IsolationLevel.READ_COMMITTED,
):
session = TRANSACTION_VAR.get()
if not session:
async with endpoint.session() as session, session.begin():
tok = TRANSACTION_VAR.set(session)
try:
await session.execute(text(f"SET TRANSACTION ISOLATION LEVEL 
{isolation_level.value}"))
yield session
finally:
TRANSACTION_VAR.reset(tok)
else:
yield session

should work automatically, and with some preliminary tests it seems it works, 
but, I don't understand why I need the manual reset. I thought just setting the 
variable would change it downstream the callstack and reset it when leaving the 
current context, as seen in the asyncio example in the documentation 
https://docs.python.org/3/library/contextvars.html#asyncio-support

Now the final question, while the current setup works, when trying to use this 
as a context manager inside an async generator and then wrapping that with like 
`asyncio.wait_for`. To illustrate this: 

--
async def gen():
with transactional():
...
yield data

g = gen()
asyncio.wait_for(g.__anext__(), 1)  # ValueError:  was created in a 
different Context
--

As far as I understand, that moves the awaitable into a new task and when 
trying to reset tok, it complains that tok was created in a different context. 
This is unfortunate as sometimes you want to be able to add a timeout using 
wait_for, particularly in tests. Is there anything I could do to my code to 
make it more reliable and robust when working with contextvars in an async 
context?

Sorry for the wall of text, hope it's understandable

Thanks in advance,
-- 
  Marce Coll
  ma...@dziban.net
-- 
https://mail.python.org/mailman/listinfo/python-list