9

I want to use context vars for a similar purpose like in this question and accepted answer: Context variables in Python

That corresponds to f3a() in this example:

import contextvars

user_id = contextvars.ContextVar("user_id_var")

def test():
    user_id.set("SOME-DATA")
    f2()

def f2():
    f3a()
    f3b()

def f3a():
    print(user_id.get()) 

def f3b():
    ctx = contextvars.copy_context()
    for key, value in ctx.items():
        if key.name == 'user_id_var':
            print(value)
            break

test()

However the function needs the user_id global variable to get the value. If it were in a different module, it would need to import it.

My idea was that if a function knows there exists a context and it knows the variable name, that should be all it needs. I wrote the f3b, but as you can see, I have to search all variables, because context vars do not support lookup by name. Lookup by variable is implemented, but if I had the variable, I could get the value directly from it (f3a case)

I'm afraid I do not understand why it was designed the way it was. Why an agreed-upon name is not a key? If a context is set in some kind of framework and then used by application code, those two functions will be in different modules without a common module global var. The examples I could find did not help me. Could somebody please explain the rationale behind the context vars API?

VPfB
  • 14,927
  • 6
  • 41
  • 75

3 Answers3

0

This is the best I worked out to make it actually make sense when I use it:

ctx = {ctx_var.name: {"context_var": ctx_var, "value": value} for ctx_var, value in copy_context().items()}
0

I was also annoyed by this interface - here's an explanation of this design choice from Chat GPT:

Python's contextvars module is designed to provide a way to manage context-specific state, such as data that should not be shared between different asynchronous tasks or threads. The design choice to use the actual ContextVar instance as a key, rather than its name, is mainly due to two reasons: encapsulation and avoidance of naming conflicts.

  1. Encapsulation: By requiring the use of the actual ContextVar instance as the key, the module encourages users to treat context variables as proper objects, rather than mere strings. This promotes encapsulation, making it easier to reason about the code and maintain it. When a context variable is accessed from a different module, it's usually better to import and use the actual ContextVar instance instead of relying on a string name. This approach reduces the risk of mistakes and naming conflicts.

  2. Avoiding naming conflicts: If the Context object allowed accessing values using the name of a ContextVar, there would be a higher risk of naming conflicts. Different modules could accidentally use the same name for different context variables, leading to unexpected behavior. By using the ContextVar instance as the key, the risk of such conflicts is significantly reduced.

While this design choice might make accessing context variables from different modules slightly less convenient, it is intended to encourage better software design practices and prevent issues caused by naming conflicts. If you need to share a ContextVar between modules, you can define the variable in a shared module and import it in the modules that need to access it.

I took the advice of instantiating ContextVars in a separate shared module - it ends up working quite nicely.

That said, if you'd still like access context with a string, here's another solution:

from contextvars import copy_context, ContextVar, Context
from typing import Optional

def get_from_context(ctx: Context, k: str) -> Optional[ContextVar]:
    try:
        return next(filter(lambda v: v.name == k, ctx.keys()))
    except StopIteration:
        return
Battery_Al
  • 779
  • 1
  • 5
  • 20
-2

You can get the value of key user_id by this way

user_id = contextvars.ContextVar("user_id_var")
ctx = contextvars.copy_context()
ctx[user_id]
# or
ctx.get(user_id)

I saw in the official document they have mention something that might related to your concern:

The notion of "current value" deserves special consideration: different asynchronous tasks that exist and execute concurrently may have different values for the same key

and Making Context objects picklable

ChickenSoups
  • 937
  • 8
  • 18