0

I have python decorator and I need pass contextVariable inside my decorator or as argument request_id in function

Step 1: Declare contextVariables and methods

_correlation_id_ctx_var: ContextVar[str] = ContextVar(CORRELATION_ID_CTX_KEY, default=None)
_request_id_ctx_var: ContextVar[str] = ContextVar(REQUEST_ID_CTX_KEY, default=None)


def get_correlation_id() -> str:
    return _correlation_id_ctx_var.get()


def get_request_id() -> str:
    return _request_id_ctx_var.get()

Step 2: The context Variable I declare inside middleware (use FastApi)

@app.middleware("http")
async def log_request(request: Request, call_next):
    correlation_id = _correlation_id_ctx_var.set(request.headers.get('X-Correlation-ID', str(uuid4())))
    request_id = _request_id_ctx_var.set(str(uuid4()))

Step3: I try to pass contextVariable to decorator - it always None Try to pass as argument in function itself - it always None

What it the problem?

Why contextVars accessible in function body only and not in decorator or argument function?

Is there any solutions to have access to contextVar before function body?

@app.get('/test')
@decorator(request_id=get_request_id())
def test_purpose(request_id=get_request_id()):
    print('get_correlation_id() start', get_request_id())
    return 'ok'

Decorator:

def decorator(request_id=None, *args, **kwargs):
    def logger(func, request_id=None, *args, **kwargs):
        @wraps(func, *args, **kwargs)
        def wrapper(*args, **kwargs):
            try:
                res = func()
                print()
                return res
            except Exception:
              pass
        return wrapper
    return logger

Dmitriy_kzn
  • 518
  • 4
  • 16

1 Answers1

1

@decorator(request_id=get_request_id()) <- this line is executed when your module is imported. Which means your getter function is called only once, when the module is imported, not each time your decorated function is called.

To fix it, just pass the getter function, not its result, to the decorator, and make the call inside the wrapper function, inside the decorator. (For that, just leave the parentheses out):



def decorator(request_id_getter=None, *d_args, **d_kw): 
    def logger(func):  # other arguments than the first positional arg here are not used at all. 
        @wraps(func, *d_args, **d_kw)
        def wrapper(*args, **kwargs):
            request_id = request_id_getter()  # and here the value is retrieved when the function is actually called
            try:
                res = func()
                print()
                return res
            except Exception:
              pass
        return wrapper
    return logger

@app.get('/test')
@decorator(request_id=get_request_id)  # <- here is the change - do not _call_ the function!
def test_purpose(request_id=get_request_id()):
    print('get_correlation_id() start', get_request_id())
    return 'ok'

jsbueno
  • 99,910
  • 10
  • 151
  • 209