tempfile em Python: arquivos temporários seguros na prática

Aprenda tempfile em Python com TemporaryDirectory, NamedTemporaryFile, SpooledTemporaryFile, testes, permissões e limpeza automática em exemplos práticos.

13 min de leitura Equipe Python Brasil

O módulo tempfile é a forma recomendada de criar arquivos e diretórios temporários em Python sem inventar nomes, disputar o mesmo caminho com outro processo ou esquecer resíduos no servidor. Ele faz parte da biblioteca padrão e oferece APIs de alto nível como TemporaryDirectory, NamedTemporaryFile, TemporaryFile e SpooledTemporaryFile.

A recomendação direta é: use um context manager (with) sempre que o temporário só for necessário durante uma operação. Escolha TemporaryDirectory para fluxos com vários artefatos; NamedTemporaryFile quando outra biblioteca precisa receber um caminho; TemporaryFile quando apenas seu código usa o objeto de arquivo; e SpooledTemporaryFile quando dados pequenos podem ficar em memória, mas os grandes devem migrar automaticamente para o disco.

Isso aparece em tarefas reais como gerar PDFs, descompactar documentos fiscais, converter imagens, preparar anexos, receber uploads e chamar programas externos. Neste guia, você vai aprender as diferenças entre as APIs, as armadilhas no Windows, como testar com pytest e como montar um processamento seguro de relatórios.

Por que não criar um arquivo com nome fixo?

Um script pequeno pode parecer funcionar assim:

from pathlib import Path

caminho = Path("/tmp/relatorio.pdf")
caminho.write_bytes(b"conteudo")

O problema aparece quando duas execuções usam relatorio.pdf ao mesmo tempo. Uma pode sobrescrever a outra, ler conteúdo incompleto ou apagar um arquivo que ainda está em uso. Em aplicações web, um nome previsível também pode abrir espaço para ataques envolvendo links simbólicos ou troca do arquivo entre a verificação e a abertura.

Acrescentar a data ou um número aleatório manualmente não resolve bem:

# Evite criar sua própria estratégia de nome temporário
caminho = Path(f"/tmp/relatorio-{cliente_id}.pdf")

Além de expor identificadores, o nome ainda pode colidir e costuma deixar a responsabilidade de limpeza espalhada pelo código. tempfile centraliza esses detalhes, escolhe a pasta temporária adequada ao sistema e cria nomes exclusivos com permissões apropriadas.

Temporário não significa sem importância. Um arquivo pode conter dados pessoais, notas fiscais, contratos ou credenciais. Trate seu conteúdo com o mesmo cuidado aplicado ao arquivo definitivo.

TemporaryDirectory: a melhor opção para uma tarefa completa

TemporaryDirectory cria uma pasta exclusiva e devolve seu caminho. Ao sair do bloco with, o Python remove a pasta e seu conteúdo.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as pasta:
    temporaria = Path(pasta)
    entrada = temporaria / "entrada.csv"
    saida = temporaria / "relatorio.txt"

    entrada.write_text("pedido,total\n101,199.90\n", encoding="utf-8")
    saida.write_text("Relatório processado", encoding="utf-8")

    print(saida.read_text(encoding="utf-8"))

# A pasta e os dois arquivos já foram removidos aqui

A limpeza ocorre mesmo quando uma exceção interrompe a operação, porque o context manager executa sua finalização. Isso torna a API especialmente útil para pipelines com várias etapas:

  1. baixar ou receber um documento;
  2. extrair arquivos;
  3. transformar o conteúdo;
  4. enviar apenas o resultado final;
  5. descartar os intermediários.

Você pode personalizar o prefixo para facilitar o diagnóstico sem tornar o nome previsível:

with TemporaryDirectory(prefix="nfe-processamento-") as pasta:
    print(pasta)

O sistema ainda acrescenta uma parte aleatória. Não dependa do formato exato do nome, pois ele é detalhe de implementação.

Mantendo o resultado que realmente importa

Tudo dentro da pasta será apagado. Se um artefato precisa sobreviver, copie ou mova-o antes de sair do bloco:

import shutil
from pathlib import Path
from tempfile import TemporaryDirectory

DESTINO = Path("relatorios/relatorio-final.csv")
DESTINO.parent.mkdir(parents=True, exist_ok=True)

with TemporaryDirectory() as pasta:
    temporario = Path(pasta) / "relatorio.csv"
    temporario.write_text("id,status\n1,ok\n", encoding="utf-8")

    shutil.copy2(temporario, DESTINO)

O artigo sobre zipfile e shutil explica quando usar copy, copy2, move e extração segura.

NamedTemporaryFile: quando você precisa de um caminho

Algumas bibliotecas aceitam um objeto de arquivo. Outras exigem um caminho no sistema, normalmente como str ou Path. NamedTemporaryFile atende ao segundo caso e expõe o nome no atributo .name.

from pathlib import Path
from tempfile import NamedTemporaryFile

with NamedTemporaryFile(
    mode="w",
    suffix=".json",
    prefix="pedido-",
    encoding="utf-8",
) as arquivo:
    arquivo.write('{"pedido_id": 101, "status": "aprovado"}')
    arquivo.flush()

    caminho = Path(arquivo.name)
    print(caminho.name)
    print(caminho.read_text(encoding="utf-8"))

O flush() envia o conteúdo do buffer do Python ao sistema operacional antes que outro leitor abra o caminho. Isso não é a mesma coisa que garantir persistência física em disco, mas normalmente é o necessário para uma biblioteca ler os bytes recém-gravados.

Use suffix quando a ferramenta identifica o formato pela extensão, como .pdf, .png ou .csv. O sufixo ajuda na interoperabilidade; ele não valida o conteúdo.

A armadilha do Windows

Reabrir um NamedTemporaryFile enquanto ele continua aberto funciona de maneira diferente entre sistemas. No Windows, o compartilhamento e a exclusão de arquivos abertos seguem regras mais restritivas. Em versões modernas do Python, as opções delete e delete_on_close permitem controlar quando o nome será removido.

Uma estratégia portátil quando uma ferramenta externa precisa abrir o caminho é fechar o arquivo antes do uso e fazer a limpeza explicitamente:

from pathlib import Path
from tempfile import NamedTemporaryFile

caminho: Path | None = None

try:
    with NamedTemporaryFile(
        mode="wb",
        suffix=".pdf",
        delete=False,
    ) as arquivo:
        arquivo.write(b"%PDF-conteudo-de-exemplo")
        caminho = Path(arquivo.name)

    # A biblioteca ou o subprocesso abre o caminho já fechado.
    print(f"Processar: {caminho}")
finally:
    if caminho is not None:
        caminho.unlink(missing_ok=True)

Ao usar delete=False, a limpeza passa a ser sua responsabilidade. Mantenha-a em um finally, não apenas no caminho de sucesso. Para uma sequência com vários arquivos, TemporaryDirectory costuma ser mais simples e menos propenso a vazamentos.

TemporaryFile: um arquivo privado para a operação

TemporaryFile devolve um objeto de arquivo que é removido automaticamente ao fechar. Em alguns sistemas, o arquivo pode nem ter uma entrada de diretório visível. Use-o quando seu código só precisa ler e escrever pelo próprio objeto e ninguém exige um caminho.

from tempfile import TemporaryFile

with TemporaryFile(mode="w+b") as arquivo:
    arquivo.write(b"linha 1\nlinha 2\n")
    arquivo.seek(0)
    conteudo = arquivo.read()

print(conteudo.decode("utf-8"))

Após escrever, seek(0) reposiciona o cursor no início. Sem isso, read() começa no fim e devolve bytes vazios.

Para texto, informe modo e encoding:

from tempfile import TemporaryFile

with TemporaryFile(mode="w+t", encoding="utf-8") as arquivo:
    arquivo.write("São Paulo; aprovado\n")
    arquivo.seek(0)
    print(arquivo.read())

Prefira essa API a NamedTemporaryFile quando o nome não tem utilidade. Menos componentes conseguem localizar ou manipular o arquivo por caminho.

SpooledTemporaryFile: memória primeiro, disco depois

SpooledTemporaryFile mantém o conteúdo em memória até atingir max_size. Depois desse limite — ou se rollover() for chamado — ele migra para um arquivo em disco.

from tempfile import SpooledTemporaryFile

with SpooledTemporaryFile(max_size=1_000_000, mode="w+b") as arquivo:
    arquivo.write(b"dados do relatorio")
    arquivo.seek(0)
    enviar_para_api(arquivo.read())

Essa opção é útil quando a maioria dos objetos é pequena, mas alguns podem crescer. Você evita acesso desnecessário ao disco nos casos comuns sem correr o risco de manter todo arquivo grande na RAM.

Um exemplo típico é preparar um CSV para upload:

import csv
import io
from tempfile import SpooledTemporaryFile

with SpooledTemporaryFile(max_size=2_000_000, mode="w+b") as binario:
    texto = io.TextIOWrapper(binario, encoding="utf-8", newline="")
    escritor = csv.DictWriter(texto, fieldnames=["pedido", "total"])
    escritor.writeheader()
    escritor.writerows(
        [
            {"pedido": 101, "total": "199,90"},
            {"pedido": 102, "total": "49,00"},
        ]
    )
    texto.flush()
    binario.seek(0)

    payload = binario.read()
    print(len(payload))

Não escolha um limite gigantesco sem medir. Em um servidor que processa muitos uploads simultâneos, alguns megabytes por requisição podem virar vários gigabytes de memória. Limites de concorrência e tamanho continuam necessários.

Como o Python escolhe a pasta temporária?

tempfile.gettempdir() mostra o diretório padrão selecionado para a execução:

import tempfile

print(tempfile.gettempdir())

O resultado varia conforme sistema operacional e variáveis de ambiente. Em Linux, é comum encontrar /tmp; no Windows, uma pasta dentro do perfil do usuário. Não grave lógica de negócio assumindo um caminho específico.

Se você precisa apenas descobrir a localização, gettempdir() é suficiente. Se precisa de um arquivo, não monte manualmente gettempdir() / nome: use as funções de criação do módulo.

mkstemp e mkdtemp são APIs de baixo nível

mkstemp() e mkdtemp() criam recursos com nomes seguros, mas não oferecem limpeza automática. mkstemp() ainda devolve um descritor de arquivo que precisa ser fechado.

import os
from pathlib import Path
from tempfile import mkstemp

fd, nome = mkstemp(suffix=".txt")
caminho = Path(nome)

try:
    with os.fdopen(fd, mode="w", encoding="utf-8") as arquivo:
        arquivo.write("resultado")
finally:
    caminho.unlink(missing_ok=True)

Na maioria dos projetos, prefira as APIs de alto nível. Use mkstemp apenas quando você realmente precisa controlar descritor, abertura e ciclo de vida.

Exemplo prático: gerar e publicar um relatório sem deixar resíduos

Imagine um serviço que recebe registros, gera um PDF em uma pasta de trabalho, compacta os comprovantes e copia apenas o pacote final para uma área de saída. Em vez de misturar intermediários com arquivos publicados, cada execução ganha uma pasta temporária exclusiva.

import shutil
from pathlib import Path
from tempfile import TemporaryDirectory


def gerar_pdf(registros: list[dict], destino: Path) -> None:
    # Substitua pela biblioteca de PDF usada no projeto.
    linhas = ["RELATÓRIO DE PEDIDOS", ""]
    linhas.extend(
        f"Pedido {item['id']}: R$ {item['total']}"
        for item in registros
    )
    destino.write_text("\n".join(linhas), encoding="utf-8")


def publicar_relatorio(registros: list[dict], saida: Path) -> Path:
    saida.mkdir(parents=True, exist_ok=True)
    pacote_final = saida / "relatorio-pedidos.zip"

    with TemporaryDirectory(prefix="relatorio-pedidos-") as pasta:
        trabalho = Path(pasta)
        artefatos = trabalho / "artefatos"
        artefatos.mkdir()
        pdf = artefatos / "relatorio.pdf"
        metadados = artefatos / "LEIA-ME.txt"

        gerar_pdf(registros, pdf)
        metadados.write_text(
            "Arquivo gerado automaticamente. Valide antes de distribuir.",
            encoding="utf-8",
        )

        base_zip = trabalho / "pacote"
        zip_criado = Path(
            shutil.make_archive(
                str(base_zip),
                format="zip",
                root_dir=artefatos,
                base_dir=".",
            )
        )

        temporario_publicacao = saida / ".relatorio-pedidos.zip.tmp"
        shutil.copy2(zip_criado, temporario_publicacao)
        temporario_publicacao.replace(pacote_final)

    return pacote_final


if __name__ == "__main__":
    resultado = publicar_relatorio(
        registros=[
            {"id": 101, "total": "199,90"},
            {"id": 102, "total": "49,00"},
        ],
        saida=Path("saida"),
    )
    print(f"Publicado em: {resultado}")

O exemplo separa três ciclos de vida:

  • os intermediários vivem apenas dentro de TemporaryDirectory;
  • o .tmp de publicação reduz o risco de expor um ZIP pela metade;
  • o arquivo final permanece em saida/.

Em uma aplicação real, use uma biblioteca apropriada para o PDF, conforme o guia de relatórios PDF com Python. Se a geração depende de LibreOffice, FFmpeg ou outra ferramenta, chame-a com argumentos em lista e timeout, seguindo as práticas de subprocess.

Uploads: o que tempfile resolve e o que não resolve

Arquivos enviados por usuários são dados não confiáveis. tempfile ajuda a criar um destino sem colisões e sem usar diretamente o nome fornecido pelo navegador, mas não substitui uma política de upload.

Aplique pelo menos estes controles:

  1. limite de tamanho antes e durante a leitura;
  2. nome gerado pelo servidor, não pelo usuário;
  3. validação de conteúdo, não apenas da extensão ou Content-Type;
  4. armazenamento fora de diretórios executáveis ou públicos;
  5. timeout e limite de memória para conversões;
  6. verificação antimalware, quando o risco exigir;
  7. remoção garantida em sucesso e falha;
  8. logs sem conteúdo sensível.

Não extraia um ZIP recebido diretamente em uma pasta sem validar os caminhos internos. Entradas como ../../arquivo podem tentar escapar do diretório de destino. O guia de zipfile citado anteriormente mostra a proteção contra path traversal.

Para dados sensíveis, lembre que apagar um arquivo não garante sobrescrita física em SSDs, sistemas com snapshots ou armazenamento em nuvem. Se o requisito é forte, minimize a gravação, use criptografia e siga a política de retenção da organização.

Testando com pytest e tmp_path

Se sua função aceita um diretório como argumento, o fixture tmp_path do pytest costuma ser melhor do que chamar tempfile dentro do teste. Ele entrega um Path exclusivo e facilita inspeções.

Código da aplicação:

from pathlib import Path


def salvar_resumo(pasta: Path, texto: str) -> Path:
    pasta.mkdir(parents=True, exist_ok=True)
    destino = pasta / "resumo.txt"
    destino.write_text(texto, encoding="utf-8")
    return destino

Teste:

from pathlib import Path


def test_salvar_resumo(tmp_path: Path) -> None:
    destino = salvar_resumo(tmp_path / "saida", "concluído")

    assert destino.exists()
    assert destino.read_text(encoding="utf-8") == "concluído"
    assert destino.parent == tmp_path / "saida"

Evite afirmar que o caminho começa com /tmp ou contém um nome específico. Isso quebra no Windows, em containers configurados de outra forma e até entre versões do Python.

Para testar uma função que usa TemporaryDirectory internamente, verifique o resultado que sai da função e, se relevante, que os intermediários não permaneceram. O tutorial de testes unitários com pytest cobre fixtures, parametrização e simulação de falhas.

Boas práticas e erros comuns

Use este checklist em automações e backends:

  1. Prefira with. A limpeza fica ligada ao escopo que usa o recurso.
  2. Use TemporaryDirectory para múltiplos artefatos. É mais simples que gerenciar vários arquivos isolados.
  3. Escolha NamedTemporaryFile apenas quando o caminho for necessário. Caso contrário, TemporaryFile reduz exposição.
  4. Chame flush() antes de outro leitor abrir o arquivo ainda ativo. Ou feche-o primeiro para maior portabilidade.
  5. Ao usar delete=False, limpe em finally. Uma exceção não pode abandonar o arquivo.
  6. Não confie na extensão. .pdf no nome não transforma bytes arbitrários em PDF válido.
  7. Não use nomes enviados pelo usuário como caminhos. Preserve o nome original apenas como metadado sanitizado, se necessário.
  8. Defina limites de tamanho e tempo. Temporários também podem esgotar disco, memória ou inodes.
  9. Não registre conteúdo sensível. Prefira um identificador interno e métricas de tamanho/duração.
  10. Monitore a pasta temporária em workers de longa duração. Resíduos indicam encerramento abrupto, delete=False sem limpeza ou processos externos travados.
  11. Use Path para manipular caminhos. Ele combina bem com tempfile e torna o código mais legível; veja o guia de pathlib.
  12. Trate erros específicos. Disco cheio, permissão e arquivo em uso devem aparecer em logs e alertas úteis, como explicado em tratamento de erros e logging.

Qual API escolher?

NecessidadeAPI recomendada
Vários arquivos de uma única tarefaTemporaryDirectory
Outra biblioteca precisa de um caminhoNamedTemporaryFile
Só seu código usa o objeto de arquivoTemporaryFile
Conteúdo pequeno em memória, grande no discoSpooledTemporaryFile
Teste que recebe uma pastapytest com tmp_path
Controle de baixo nível do descritormkstemp, com fechamento e limpeza manuais

Se estiver em dúvida, comece com TemporaryDirectory. Uma pasta de trabalho exclusiva deixa claro o que é intermediário, funciona bem com ferramentas externas e simplifica a limpeza de fluxos complexos.

Perguntas frequentes

O módulo tempfile precisa ser instalado com pip?

Não. tempfile faz parte da biblioteca padrão do Python. Basta importar TemporaryDirectory, NamedTemporaryFile, TemporaryFile ou SpooledTemporaryFile. Bibliotecas externas só são necessárias para o restante do fluxo, como gerar PDF, processar imagens ou enviar arquivos a um serviço de nuvem.

Qual a diferença entre TemporaryFile e NamedTemporaryFile?

TemporaryFile cria um arquivo que pode não ter um caminho reutilizável por outro programa, dependendo da plataforma. NamedTemporaryFile garante um nome acessível pelo atributo .name, sendo a escolha comum quando uma biblioteca ou subprocesso exige um caminho. No Windows, reabrir um arquivo ainda aberto exige atenção às opções de exclusão.

Quando devo usar TemporaryDirectory?

Use TemporaryDirectory quando uma tarefa produz vários arquivos relacionados, como páginas extraídas de um PDF, XMLs descompactados, miniaturas ou artefatos de uma ferramenta externa. O context manager cria uma pasta exclusiva e remove a árvore ao sair do bloco, inclusive quando ocorre uma exceção.

tempfile torna qualquer upload seguro?

Não. O módulo reduz riscos de nomes previsíveis e colisões, mas o conteúdo recebido ainda precisa de limites de tamanho, validação de tipo e tratamento como dado não confiável. Não execute o arquivo, não use o nome enviado pelo usuário como comando e não confie apenas na extensão.

Como testar código que cria arquivos temporários?

Para funções que recebem um diretório, use o fixture tmp_path do pytest, pois ele fornece um pathlib.Path isolado por teste. Para testar o uso interno de tempfile, valide o resultado observável e a limpeza após o bloco. Não dependa do caminho exato da pasta temporária.

Conclusão

tempfile resolve uma parte invisível, mas importante, do software profissional: criar espaço de trabalho sem colisões e encerrá-lo sem deixar resíduos. A regra prática é combinar TemporaryDirectory ou um arquivo temporário com with, copiar apenas o resultado que deve sobreviver e manter validação, limites e tratamento de erros ao redor do fluxo.

A API correta depende de quem consumirá o temporário. Se uma ferramenta exige caminho, use NamedTemporaryFile ou uma pasta temporária; se apenas seu código precisa de leitura e escrita, TemporaryFile é suficiente; se o tamanho varia muito, considere SpooledTemporaryFile. Com pathlib, testes em tmp_path, logs úteis e limpeza em finally nos casos manuais, seus scripts ficam mais portáteis e confiáveis — exatamente o tipo de cuidado valorizado em automações, backends e vagas de Python.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português