Las aplicaciones modernas de Python procesan muchas solicitudes y trabajos al mismo tiempo. Un servidor web cambia entre corrutinas cada vez que encuentra un await. En ese entorno, una variable global no es segura para guardar información específica de una solicitud. Un identificador puede terminar en el registro de otra operación, un tenant puede heredar datos de otro flujo y una función profunda puede necesitar parámetros adicionales solo para transportar metadatos. El módulo contextvars resuelve este problema mediante variables cuyo valor pertenece al contexto de ejecución actual.
En esta guía aprenderás cómo funciona ContextVar, por qué conviene conservar los tokens, cómo se integra con asyncio, cómo propagar contexto a hilos y cómo usarlo para correlation IDs. También puedes consultar nuestros artículos sobre observabilidad con structlog, Pydantic Settings, tomllib y singledispatch.
Por qué fallan las variables globales
Imagina que la solicitud A guarda el identificador A123 y se detiene mientras espera una consulta. La solicitud B continúa y reemplaza el valor por B456. Cuando A se reanuda, su mensaje de log puede contener el identificador de B. Las pruebas secuenciales pueden pasar, pero el error aparece en producción bajo concurrencia.
Pasar el identificador por todos los parámetros evita el estado global, aunque genera firmas ruidosas y acoplamiento. Un repositorio o cliente HTTP quizá no utilice el valor directamente. Una variable de contexto mantiene los metadatos disponibles en el flujo actual sin convertirlos en estado global compartido.
Crear una ContextVar
Declara la variable una sola vez a nivel de módulo y utiliza un nombre descriptivo. Puedes definir un valor predeterminado para metadatos opcionales. El método get() devuelve el valor asociado al contexto actual y set() asigna uno nuevo. Si no hay valor predeterminado ni asignación previa, get() produce LookupError. Este comportamiento estricto resulta útil cuando la aplicación debe inicializar obligatoriamente el contexto.
No crees una ContextVar distinta en cada solicitud. El objeto debe permanecer estable; lo que cambia entre contextos es su valor. Las anotaciones de tipos ayudan a documentar el contenido esperado.
Restaurar el valor con tokens
El método set() devuelve un token que representa el estado anterior. Guarda ese token y úsalo con reset() al finalizar la operación. La restauración debe ocurrir dentro de un bloque finally para que también se ejecute cuando haya una excepción.
Asignar manualmente un valor genérico al final no es equivalente. Una capa superior puede haber establecido otro contexto y el token permite recuperar exactamente ese estado. Este patrón evita que procesos de larga duración conserven metadatos obsoletos para trabajos posteriores.
Aislamiento con asyncio
Las variables de contexto están diseñadas para funcionar con las tareas de asyncio. Dos corrutinas pueden asignar valores diferentes, alternar su ejecución en varios puntos de espera y seguir leyendo el valor correcto. El runtime mantiene el contexto asociado a cada tarea y lo restaura cuando dicha tarea vuelve a ejecutarse.
La documentación oficial de contextvars describe la API. La documentación de asyncio explica cómo se programan las corrutinas. Ambas fuentes ayudan a comprender el aislamiento.
Correlation IDs en los registros
Un caso práctico consiste en asignar un correlation ID a cada solicitud HTTP. La capa de entrada acepta un identificador o genera uno nuevo, lo guarda en una ContextVar y permite que filtros de logging, procesadores de structlog, clientes HTTP y servicios internos lo consulten.
Todos los eventos de una solicitud reciben el mismo campo. El equipo puede buscar ese identificador y reconstruir el recorrido completo. El mismo enfoque funciona para trace IDs, tenant, idioma o actor técnico. Conviene guardar valores pequeños y evitar objetos completos de usuario o estructuras grandes.
No usar el contexto como almacén oculto
ContextVar no reemplaza los parámetros, los objetos de dominio, una base de datos ni la inyección de dependencias. Los valores que determinan la lógica del negocio deberían permanecer explícitos. El contexto es apropiado para información transversal como observabilidad, auditoría, localización y metadatos de infraestructura.
Una regla útil es preguntar si el valor existe principalmente para registrar, rastrear o identificar el flujo. Si cambia el resultado principal de una función, es preferible mostrar la dependencia en su firma.
Hilos y ejecutores
Cada hilo mantiene su propia pila de contextos. La propagación entre tareas de asyncio suele ser automática, pero los pools de hilos personalizados requieren atención. La función copy_context() captura el contexto actual y permite ejecutar una función dentro de esa copia.
No todas las bibliotecas externas propagan el contexto de la misma manera. Cuando una aplicación combina asyncio, callbacks, executors y workers propios, crea una prueba de integración. La prueba debe establecer un identificador antes de despachar el trabajo y comprobar que el worker observa el valor esperado.
Pruebas de código con contexto
Las pruebas deben restaurar el contexto después de cada escenario. Un fixture de pytest puede asignar un valor, ceder el control al test y ejecutar reset() en la limpieza. Esto impide que una prueba influya en la siguiente.
También conviene probar concurrencia. Ejecuta dos corrutinas con valores distintos, introduce un punto de espera y confirma que cada tarea conserva su valor. Esa prueba protege frente a refactorizaciones que sustituyan accidentalmente la variable de contexto por una global.
Errores frecuentes
El error más común es llamar a set() y olvidar el token. Otro problema es guardar objetos mutables y modificarlos en el lugar. Prefiere cadenas, números o estructuras pequeñas e inmutables. Tampoco debes tratar el aislamiento del contexto como una frontera de seguridad: no cifra los datos ni impide que el código del mismo flujo los lea.
Un valor predeterminado demasiado genérico también puede ocultar fallos. Un texto como desconocido hace que los logs parezcan completos incluso cuando el middleware olvidó inicializar el contexto. Para metadatos obligatorios, puede ser mejor no definir default y permitir un error claro.
Organización recomendada
Coloca las variables en un módulo dedicado y expone funciones pequeñas como get_request_id() y un context manager para definir el valor temporal. El context manager realiza set(), entrega el control y restaura el token en finally. El middleware del framework y las pruebas pueden compartir la misma API.
Centralizar el acceso evita patrones de limpieza diferentes en cada archivo, facilita la documentación y permite añadir validaciones sin modificar toda la aplicación.
Cuándo usar contextvars
Usa variables de contexto cuando un metadato deba estar disponible en varias capas de un mismo flujo, especialmente en servicios asíncronos. Los casos habituales incluyen identificadores de solicitud, trazas, tenant, locale y campos de logging estructurado. Usa parámetros explícitos cuando el valor pertenezca a la lógica del negocio.
Conclusión
El módulo contextvars proporciona estado contextual seguro para aplicaciones concurrentes en Python. Evita colisiones de variables globales, reduce el transporte innecesario de parámetros y se integra bien con asyncio, logging estructurado, tracing y auditoría. Declara las variables una sola vez, restaura los valores temporales con tokens, guarda solo metadatos ligeros y prueba la concurrencia. Con estas prácticas obtendrás mejor observabilidad sin perder el aislamiento entre solicitudes y trabajos en segundo plano.






