El módulo contextvars almacena información asociada al contexto actual de ejecución. A diferencia de una variable global, un ContextVar puede tener valores distintos en tasks asíncronas, threads y contextos copiados. Es útil para IDs de request, tracing, usuario actual, locale, transacciones y datos de observabilidad que deben acompañar una cadena de llamadas sin pasarse manualmente en cada función.
Las context variables no deberían sustituir parámetros explícitos para datos de negocio. Funcionan mejor para contexto transversal y controlado. Demasiado estado oculto dificulta tests y comprensión. Define quién configura el valor, cuándo se restaura y hasta dónde puede propagarse.
Crea un ContextVar
Decláralo normalmente a nivel de módulo.
from contextvars import ContextVar
request_id = ContextVar("request_id")
El nombre aparece en diagnósticos. Elige un identificador claro y estable.
Define y lee un valor
set() asocia un valor al contexto actual y get() lo recupera.
request_id.set("req-123")
print(request_id.get())
El valor no es una global compartida por todo el proceso; cada contexto puede tener su propia asociación.
Valores predeterminados
Se puede definir un default en el constructor.
locale_actual = ContextVar("locale_actual", default="es-ES")
Usa default solo cuando la ausencia sea válida. Para contexto obligatorio, omitirlo ayuda a detectar configuración faltante.
LookupError
Llamar get() sin valor ni default lanza LookupError.
try:
identificador = request_id.get()
except LookupError:
identificador = "sin-contexto"
No ocultes automáticamente el error cuando el contexto debería existir.
Tokens
set() devuelve un Token que representa el estado anterior.
token = request_id.set("req-456")
try:
ejecutar()
finally:
request_id.reset(token)
El patrón try/finally evita que el valor se filtre a trabajo posterior en el mismo contexto.
Restaura, no solo limpies
reset(token) restaura el estado anterior, que puede ser otro valor o ausencia.
Asignar None no es equivalente y puede destruir un contexto exterior que debería volver después.
Crea un context manager
Un wrapper vuelve reutilizable el patrón set/reset.
from contextlib import contextmanager
@contextmanager
def usar_request_id(valor):
token = request_id.set(valor)
try:
yield
finally:
request_id.reset(token)
Consulta contextlib en Python.
Integración con asyncio
Las tasks normalmente reciben una copia apropiada del contexto actual al ser creadas.
import asyncio
async def worker(nombre):
print(nombre, request_id.get())
async def main():
token = request_id.set("req-main")
try:
await asyncio.gather(worker("a"), worker("b"))
finally:
request_id.reset(token)
Cada task puede cambiar su valor sin sobrescribir a las otras.
Momento de creación de la task
El instante en que una task se crea influye en el contexto capturado.
Define el valor antes de crearla cuando necesite heredarlo. Usa opciones explícitas de contexto disponibles en la versión objetivo cuando sea necesario.
No uses threading.local en async
threading.local() separa datos por thread, pero muchas tasks asíncronas comparten una sola thread.
ContextVar conserva aislamiento lógico entre esas tasks.
Threads
Cada thread tiene su propia pila de contextos. Los valores no aparecen automáticamente en una nueva thread.
Copia el contexto explícitamente o pasa los datos como argumentos.
copy_context
copy_context() crea una copia superficial del contexto actual.
from contextvars import copy_context
contexto = copy_context()
contexto.run(funcion)
Las asociaciones se copian, pero los objetos mutables usados como valores siguen siendo los mismos.
Propaga a un executor
Captura el contexto antes de enviar trabajo a una thread.
contexto = copy_context()
futuro = executor.submit(contexto.run, procesar, item)
No entres simultáneamente en el mismo Context desde varias threads. Crea una copia por envío.
Valores mutables
Guardar un diccionario o lista en un ContextVar no lo vuelve inmutable ni aislado.
Prefiere valores inmutables o copia antes de modificar. De lo contrario, varios contextos pueden compartir estado interno.
IDs de request
Un middleware puede definir el ID al entrar y restaurarlo al salir.
def atender(request):
token = request_id.set(request.id)
try:
return procesar(request)
finally:
request_id.reset(token)
La asociación exterior vuelve incluso después de una excepción.
Logging
Filtros o adapters pueden leer el valor actual y añadirlo al registro.
class ContextFilter(logging.Filter):
def filter(self, record):
record.request_id = request_id.get("-")
return True
No guardes contraseñas, tokens o datos personales innecesarios en el contexto de logging.
Tracing
Trace ID y span ID son ejemplos comunes de contexto transversal.
Las bibliotecas de observabilidad pueden usar contextvars internamente. Integra mediante su API pública para evitar fuentes de verdad duplicadas.
Locale y timezone
Una aplicación puede guardar locale o timezone por request.
Valida y restaura el valor. Las funciones puras con parámetros explícitos siguen siendo más fáciles de probar.
Transacciones
Un identificador o sesión puede ser contextual, pero conexiones, commit y rollback poseen lifecycle crítico.
No ocultes el cierre. Combina contextvars con context managers y ownership explícito.
Callbacks
El contexto de ejecución de un callback depende de cuándo y cómo fue registrado.
No supongas propagación en bibliotecas externas. Haz tests de integración o captura el contexto.
Background tasks
Una tarea en background no debería heredar todo el contexto de una request, especialmente credenciales y objetos grandes.
Crea un contexto limpio o copia únicamente los campos necesarios.
Context.run
Context.run(callable, ...) entra en el contexto, ejecuta la función y restaura el anterior.
resultado = contexto.run(funcion, argumento)
Las excepciones se propagan normalmente.
Inspecciona un contexto
Un objeto Context puede recorrerse como mapping.
for variable, valor in copy_context().items():
print(variable.name, valor)
Clasifica sensibilidad antes de registrar valores.
Rendimiento
Las operaciones son eficientes para contexto transversal normal, pero no deberían sustituir variables locales en loops críticos.
Mide antes de añadir muchas lecturas en caminos muy calientes.
APIs públicas
Una biblioteca puede usar contextvars internamente, pero debe documentar cómo se establece y restaura el contexto.
No obligues a usuarios a manipular tokens privados.
Pruebas
Cada test debe establecer el contexto que necesita y restaurarlo después.
token = request_id.set("test")
try:
assert ejecutar() == esperado
finally:
request_id.reset(token)
Prueba tasks concurrentes con valores distintos para detectar filtraciones.
Aislamiento entre tests
Fixtures que olvidan resetear pueden contaminar casos posteriores en la misma thread.
Usa context managers o fixtures con cleanup garantizado.
Seguridad
El contexto implícito puede transportar identidad o autorización. Nunca confíes únicamente en ese valor sin validar la operación.
Reduce propagación a background jobs y no expongas todo el contexto en errores públicos.
Errores comunes
Los fallos frecuentes son usar globales o threading.local en asyncio, olvidar reset(), asignar None en vez de restaurar token, guardar objetos mutables, suponer propagación a threads y ocultar datos de negocio en contexto.
Conclusión
contextvars ofrece contexto local a la ejecución para threads y tasks async. Usa ContextVar para datos transversales, tokens para restaurar y copy_context() para propagación explícita.
Mantén valores pequeños, evita secretos, prueba aislamiento y usa parámetros explícitos para reglas de negocio. Consulta la documentación oficial de contextvars y contextlib en Python.







