O módulo contextvars permite armazenar informações associadas ao contexto atual de execução. Diferente de uma variável global, um ContextVar pode ter valores distintos em tasks assíncronas, threads e contextos copiados. Isso é útil para IDs de requisição, tracing, usuário atual, locale, transações e dados de observabilidade que precisam acompanhar uma cadeia de chamadas sem ser passados manualmente em todos os argumentos.
Context variables não substituem parâmetros explícitos para dados de negócio. Elas são adequadas para contexto transversal e controlado. Valores ocultos demais dificultam testes e compreensão. Defina ownership, momento de configuração, restauração e limites de lifecycle.
Crie um ContextVar
Declare a variável normalmente no nível do módulo.
from contextvars import ContextVar
request_id = ContextVar("request_id")
O nome aparece em diagnóstico e representação. Use um identificador claro e estável.
Defina e leia um valor
set() associa um valor ao contexto atual e get() recupera.
request_id.set("req-123")
print(request_id.get())
O valor não é simplesmente uma global compartilhada; cada contexto pode possuir sua própria associação.
Valores padrão
Um default pode ser definido no construtor.
locale_atual = ContextVar("locale_atual", default="pt-BR")
Use default apenas quando a ausência for realmente válida. Para valores obrigatórios, deixar sem default ajuda a detectar configuração esquecida.
LookupError
Chamar get() sem valor e sem default gera LookupError.
try:
identificador = request_id.get()
except LookupError:
identificador = "sem-contexto"
Não esconda automaticamente um erro quando o contexto deveria ser obrigatório.
Tokens
set() retorna um Token que representa o estado anterior.
token = request_id.set("req-456")
try:
executar()
finally:
request_id.reset(token)
O padrão try/finally evita que o valor vaze para trabalho executado depois no mesmo contexto.
Restaure, não apenas limpe
reset(token) restaura o estado anterior, que pode ser outro valor ou ausência.
Definir None não é equivalente: pode destruir um valor externo que deveria voltar depois do bloco.
Crie um context manager
Um wrapper torna o padrão de set/reset reutilizável.
from contextlib import contextmanager
@contextmanager
def usar_request_id(valor):
token = request_id.set(valor)
try:
yield
finally:
request_id.reset(token)
Consulte contextlib no Python para managers síncronos e assíncronos.
Integração com asyncio
Tasks criadas em um contexto normalmente recebem uma cópia apropriada do contexto atual.
import asyncio
async def worker(nome):
print(nome, 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 pode alterar seu próprio valor sem sobrescrever o contexto das outras.
Criação de tasks
O momento em que uma task é criada influencia o contexto capturado.
Defina o valor antes de criar a task quando ela precisa herdar a associação. Para controle explícito, use APIs da versão alvo que aceitem um contexto.
Não use threading.local em código async
threading.local() separa dados por thread, mas várias tasks assíncronas compartilham a mesma thread.
ContextVar foi projetado para manter isolamento lógico entre essas tasks.
Threads
Cada thread possui sua própria pilha de contextos. Um valor não aparece automaticamente em uma nova thread.
Se um worker precisa do contexto do caller, copie e execute explicitamente ou passe os dados como argumentos.
copy_context
copy_context() cria uma cópia rasa do contexto atual.
from contextvars import copy_context
contexto = copy_context()
contexto.run(executar_funcao)
A cópia registra associações de variáveis, mas objetos mutáveis usados como valores continuam sendo os mesmos objetos.
Propague para um executor
Ao enviar trabalho a uma thread, capture o contexto antes.
contexto = copy_context()
futuro = executor.submit(contexto.run, processar, item)
Não execute simultaneamente o mesmo objeto Context em várias threads. Crie uma cópia para cada submissão quando necessário.
Valores mutáveis
Colocar um dicionário ou lista em um ContextVar não torna o objeto imutável ou isolado.
Prefira valores imutáveis ou crie uma cópia antes de modificar. Caso contrário, dois contextos podem compartilhar a mesma estrutura interna.
IDs de requisição
Middleware pode definir o ID ao entrar e restaurar ao sair.
def atender(request):
token = request_id.set(request.id)
try:
return processar(request)
finally:
request_id.reset(token)
Mesmo em caso de exceção, a associação anterior volta corretamente.
Logging
Filtros ou adapters de logging podem ler o valor atual e adicioná-lo aos registros.
class ContextFilter(logging.Filter):
def filter(self, record):
record.request_id = request_id.get("-")
return True
Evite armazenar tokens, senhas e dados pessoais no contexto de log.
Tracing
Trace ID e span ID são exemplos comuns de contexto transversal.
Bibliotecas de observabilidade podem usar contextvars internamente. Integre com a API oficial para não criar duas fontes de verdade.
Locale e timezone
Uma aplicação pode armazenar preferência de locale ou timezone por requisição.
O valor precisa ser validado e resetado. Funções puras que recebem locale explicitamente continuam mais fáceis de testar.
Transações
Um identificador ou objeto de sessão pode ser contextual, mas conexões e transações têm lifecycle crítico.
Não esconda commit, rollback e fechamento. Combine contextvars com context managers e ownership explícito.
Callbacks
O contexto em que um callback executa pode depender de quando e como ele foi registrado.
Não presuma propagação em bibliotecas de terceiros. Faça um teste de integração ou capture o contexto explicitamente.
Background tasks
Uma tarefa em background não deve herdar automaticamente todo o contexto de uma requisição, especialmente credenciais e objetos grandes.
Crie um contexto limpo ou copie somente os campos necessários.
Context.run
Context.run(callable, ...) entra no contexto, executa a função e sai depois.
resultado = contexto.run(funcao, argumento)
Uma exceção propaga normalmente, e o contexto anterior da thread é restaurado.
Inspecione um contexto
Um objeto Context pode ser tratado como mapping para diagnóstico.
for variavel, valor in copy_context().items():
print(variavel.name, valor)
Não registre valores completos sem classificação de sensibilidade.
Performance
Operações de contexto são eficientes para uso transversal comum, mas não devem substituir variáveis locais em loops internos.
Meça antes de colocar dezenas de leituras em caminhos extremamente quentes.
APIs públicas
Uma biblioteca pode usar contextvars internamente, mas deve documentar como o contexto é definido e restaurado.
Evite exigir que usuários manipulem tokens internos ou dependam do nome de uma variável privada.
Testes
Todo teste deve estabelecer o contexto que usa e restaurá-lo no final.
token = request_id.set("teste")
try:
assert executar() == esperado
finally:
request_id.reset(token)
Teste tasks concorrentes com valores diferentes para detectar vazamentos.
Isolamento entre testes
Fixtures que esquecem o reset podem contaminar casos seguintes executados na mesma thread.
Use context managers ou fixtures com cleanup garantido.
Segurança
Contexto implícito pode transportar identidade e autorização. Nunca confie apenas em um valor contextual sem validar a operação.
Reduza a propagação para background jobs e não exponha o conteúdo em mensagens de erro públicas.
Erros comuns
Os erros mais frequentes são usar global ou threading.local em asyncio, esquecer reset(), definir None em vez de restaurar token, armazenar objetos mutáveis, presumir propagação para threads e usar contexto oculto para dados de negócio.
Conclusão
contextvars fornece contexto local à execução para threads e tasks assíncronas. Use ContextVar para dados transversais, tokens para restauração e copy_context() para propagação explícita.
Mantenha valores pequenos, evite segredos, teste isolamento e continue usando parâmetros explícitos para regras de negócio. Consulte a documentação oficial de contextvars e contextlib no Python.







