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()