contextvars en Python: contexto seguro

Publicado el: 26/07/2026
Tempo de leitura: 6 minutos
Código Python para contexto seguro en aplicaciones asíncronas

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código Python con cached_property para guardar cálculos costosos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    cached_property en Python: caché en objetos

    Aprende cached_property en Python para guardar cálculos costosos, invalidar valores y evitar cachés desactualizadas.

    Ler mais

    Tempo de leitura: 7 minutos
    25/07/2026
    Código Python con funciones especializadas por tipo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    singledispatch en Python: funciones por tipo

    Aprende singledispatch en Python para crear funciones por tipo, reducir cadenas isinstance y organizar polimorfismo extensible con ejemplos.

    Ler mais

    Tempo de leitura: 7 minutos
    25/07/2026
    Código Python y atributos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Descriptors en Python: guía práctica

    Aprende descriptors en Python con __get__, __set__, validación, property, almacenamiento por instancia, pruebas, herencia y buenas prácticas.

    Ler mais

    Tempo de leitura: 7 minutos
    22/07/2026
    Leitura de arquivos grandes em Python sem travar o sistema
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Cómo leer archivos gigantes con Python sin bloquear el sistema

    Aprende a leer archivos gigantes con Python usando iteración, bloques, generadores, chunks de Pandas, compresión y puntos de control.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Herança múltipla em Python sem causar problemas no código
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Herencia múltiple en Python: MRO, super() y mixins

    Aprende herencia múltiple en Python con MRO, super(), mixins, problema del diamante, inicializadores cooperativos, composición y pruebas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Criando instalador EXE com ícone personalizado em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un ejecutable EXE con icono personalizado en Python

    Crea un EXE de Python con PyInstaller, icono ICO, onefile, windowed, archivos de datos, SPEC, rutas seguras, pruebas y distribución.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026