Python contextvars: ID запроса в asyncio-задачах

Категория: Python · Язык: Python · Обновлено:

ContextVar хранит значение для текущего асинхронного контекста, а не в одной общей глобальной переменной. asyncio копирует контекст при создании задачи, поэтому параллельные запросы могут безопасно использовать разные request ID. Устанавливайте значение на границе обработки и сбрасывайте его в finally, чтобы оно не просочилось в последующую работу.

Передать request ID дочерним задачам

Python
import asyncio
from contextvars import ContextVar

request_id = ContextVar("request_id", default="-")

async def log_step(step: str) -> None:
    await asyncio.sleep(0)
    print(f"{request_id.get()} {step}")

async def handle_request(value: str) -> None:
    token = request_id.set(value)
    try:
        await asyncio.gather(
            log_step("load"),
            log_step("save"),
        )
    finally:
        request_id.reset(token)

async def main() -> None:
    await asyncio.gather(
        handle_request("req-a"),
        handle_request("req-b"),
    )

asyncio.run(main())

В выводе шаги одного запроса сохранят общий ID, даже если задачи выполняются вперемешку. Context variables поддерживаются asyncio с Python 3.7. В веб-приложении ставьте значение в middleware и сбрасывайте в том же контексте; для фоновой задачи передавайте ID явно или создавайте её внутри нужного контекста.

Документация: contextvars, asyncio tasks.