Envio de Arquivos e Dados de Formulário com Python Requests e Toolbelt

Em testes de API, é frequente a necessidade de validar endpoints que aceitam o envio de arquivos, como imagens, documentos ou anexos diversos. Neste guia, exploraremos como simular essas requisições de upload utilizando a biblioteca requests do Python e a extensão requests_toolbelt.

Abordagem 1: Utilizando o método POST nativo do Requests

A biblioteca requests permite o envio de arquivos através do parâmetro files em requisições POST. Abaixo, demonstramos como manter uma sessão, extrair um token CSRF e realizar o upload de uma imagem.

import re
import requests

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}

def execute_upload():
    session = requests.Session()
    
    # Simulação de autenticação prévia
    # authenticate(session)

    # Endpoint para obtenção do token CSRF
    base_endpoint = 'http://api.exemplo.com/admin/media/'
    response = session.get(base_endpoint)
    csrf_match = re.search(r"name='csrfmiddlewaretoken' value='(.*?)'", response.text)
    csrf_token = csrf_match.group(1) if csrf_match else ""

    form_data = {
        "csrfmiddlewaretoken": csrf_token,
        "description": "imagem_teste_01"
    }

    # Uso de context manager para garantir o fechamento do arquivo
    with open('sample_image.png', 'rb') as img_file:
        uploaded_files = [
            ('media_file', ('sample_image.png', img_file, 'image/png'))
        ]
        
        upload_endpoint = 'http://api.exemplo.com/admin/media/upload/'
        post_response = session.post(
            upload_endpoint, 
            headers=HEADERS, 
            data=form_data, 
            files=uploaded_files
        )
        print(post_response.json())

if __name__ == '__main__':
    execute_upload()

Desafio: Múltiplos arquivos com o mesmo nome de parâmetro

Ao capturar requisições de rede, você pode notar que múltiplos arquivos são enviados com o mesmo nome de campo (ex: files[]). Como dicionários em Python não permitem chaves duplicadas, a estrutura de dicionário padrão falhará. A solução é utilizar uma lista de tuplas:

# Estrutura incorreta (dicionário não permite chaves duplicadas)
# invalid_files = {"files[]": open("1.png", "rb"), "files[]": open("2.png", "rb")}

# Estrutura correta para múltiplos arquivos no mesmo campo
with open('doc1.png', 'rb') as f1, open('doc2.png', 'rb') as f2:
    valid_files = [
        ("files[]", ("doc1.png", f1, "image/png")),
        ("files[]", ("doc2.png", f2, "image/png"))
    ]

Abordagem 2: Construindo payloads complexos com requests_toolbelt

Para cenários mais robustos, especialmente quando a ordem dos campos ou a repetição de chaves é estritamente exigida pelo servidor, o módulo requests_toolbelt é a escolha ideal. Instale-o via pip:

pip install requests-toolbelt

O MultipartEncoder facilita a construção de corpos de requisição multipart/form-data, lidando perfeitamente com a mistura de campos de texto e binários.

Envio de dados de formulário e arquivos

from requests_toolbelt import MultipartEncoder
import requests

# Exemplo de upload de arquivo
with open('script.py', 'rb') as py_file:
    multipart_payload = MultipartEncoder(
        fields={
            'status': 'active',
            'category': 'scripts',
            'attachment': ('script.py', py_file, 'text/plain')
        }
    )

    api_response = requests.post(
        'https://httpbin.org/post',
        data=multipart_payload,
        headers={'Content-Type': multipart_payload.content_type}
    )

Utilizando listas no parâmetro feilds para chaves duplicadas

Assim como no método nativo, o MultipartEncoder aceita uma lista de tuplas no parâmetro fields. Isso é crucial quando o servidor espera múltiplos valores para a mesma chave, como tokens CSRF repetidos ou arrays de formulários.

import re
import requests
from requests_toolbelt import MultipartEncoder

def advanced_upload():
    session = requests.Session()
    
    # authenticate(session)

    token_endpoint = 'http://api.exemplo.com/admin/media/'
    res = session.get(token_endpoint)
    csrf_token = re.search(r"name='csrfmiddlewaretoken' value='(.*?)'", res.text).group(1)

    with open('report.png', 'rb') as image_file:
        # A lista permite a repetição da chave 'csrfmiddlewaretoken' e outros campos
        encoder = MultipartEncoder(
            fields=[
                ("csrfmiddlewaretoken", csrf_token),
                ("csrfmiddlewaretoken", csrf_token), # Servidores que exigem tokens duplicados
                ("title", "relatorio_final"),
                ("action", "save"),
                ("tags", ""),
                ("media_file", ('report.png', image_file, "image/png"))
            ]
        )
        
        target_endpoint = 'http://api.exemplo.com/admin/media/upload/'
        final_response = session.post(
            target_endpoint,
            data=encoder,
            headers={"Content-Type": encoder.content_type}
        )
        print(final_response.text)

if __name__ == '__main__':
    advanced_upload()

Tags: Python Requests requests-toolbelt multipart-form-data API-Testing

Publicado em 7-28 16:23