Search code examples
pythonpython-asynciofastapihttpx

uploading multiple files UploadFiles FastAPI


Example

Here's my code:

from typing import  List
from fastapi import FastAPI, File, UploadFile
import asyncio
import concurrent.futures

app = FastAPI()
@app.post("/send_images")
async def update_item(
    files: List[UploadFile] = File(...),
):
    return {"res": len(files)}

And I send requests to this server with (these specific urls are just for example here):

import requests
import os 
import json
import numpy as np
import time
import io
import httpx
import asyncio
from datetime import datetime
import pandas as pd

from random import shuffle
import pandas as pd
import urllib
import io
from PIL import Image
from matplotlib import pyplot as plt
import concurrent.futures

urls = ['https://sun9-63.userapi.com/c638920/v638920705/1a54d/xSREwpakJD4.jpg',
 'https://sun9-28.userapi.com/c854024/v854024084/1160d8/LDMVHYgguAw.jpg',
 'https://sun9-54.userapi.com/c854220/v854220084/111f66/LdcbEpPR6tg.jpg',
 'https://sun9-40.userapi.com/c841420/v841420283/4c8bb/Mii6GSCrmpo.jpg',
 'https://sun6-16.userapi.com/CPQpllJ0KtaArvQKkPsHTZDCupqjRJ_8l07ejA/iyg2hRR_kM4.jpg',
 'https://sun9-1.userapi.com/c638920/v638920705/1a53b/SMta6Bv-k7s.jpg',
 'https://sun9-36.userapi.com/c857332/v857332580/56ad/rJCGKFw03FQ.jpg',
 'https://sun6-14.userapi.com/iPsfmW0ibE8RsMh0k2lUFdRxHZ4Q41yctB7L3A/ajJHY3WN6Xg.jpg',
 'https://sun9-28.userapi.com/c854324/v854324383/1c1dc3/UuFigBF7WDI.jpg',
 'https://sun6-16.userapi.com/UVXVAT-tYudG5_24FMaBWTB9vyW8daSrO2WPFQ/RMjv7JZvowA.jpg']

os.environ['NO_PROXY'] = '127.0.0.1'

async def request_get_4(list_urls):
    async with httpx.AsyncClient() as client:
        r = httpx.post("http://127.0.0.1:8001/send_images", files={f'num_{ind}': el for ind, el in enumerate(list_urls)})
        print(r.text)
        return r

async def request_get_3(url):
    async with httpx.AsyncClient() as client:
        return await client.get(url)
    
from collections import defaultdict

async def main():
    start = datetime.now()
    tasks = [asyncio.create_task(request_get_3(url)) for url in urls[0:10]]
    result = await asyncio.gather(*tasks)
    
    data_to_send = []
    for ind, resp in enumerate(result):
        if resp.status_code == 200:
            image_bytes = io.BytesIO(resp.content)
            image_bytes.seek(0)
            data_to_send.append(image_bytes)
        
    end = datetime.now()
    print(result)
    print(len(data_to_send))

    batch_size = 2
    batch_num = len(data_to_send) // batch_size
    tasks = [asyncio.create_task(request_get_4(data_to_send[i * batch_size: (i+1) * batch_size])) for i in range(batch_num)]
    result = await asyncio.gather(*tasks)
    
    left_data = data_to_send[batch_size*(batch_num):]
    print(len(left_data))
    print(result)

asyncio.run(main())

I am trying to load image which are contained in urls, then form batches of them and send them to FastAPI server. But it doesn't work. I get the following error:

{"detail":[{"loc":["body","files"],"msg":"field required","type":"value_error.missing"}]}

How can I fix my problem and be able to send multiple files through httpx to FastAPI?


Solution

  • The problem is that HTTPX 0.13.3 doesn't support multiple file uploading as this arg wants dictionary and dictionary can't have same key values.

    We can use this pull request https://github.com/encode/httpx/pull/1032/files to fix this problem (now it can also accept List[Tuple[str, FileTypes]]])!

    UPDATE: this problem is solved now!

    Update2: Here I show how I use httpx for different requests:

    async def request_post_batch(fastapi_url: str, url_content_batch: List[BinaryIO]) -> httpx.Response:
        """
        Send batch to FastAPI server.
        """
        async with httpx.AsyncClient(timeout=httpx.Timeout(100.0)) as client:
            r = await client.post(
                fastapi_url,
                files=[('bytes_image', url_content) for url_content in url_content_batch]
            )
            return r
    
    
    async def request_post_logs(logstash_url: str, logs: List[Dict]) -> httpx.Response:
        """
        Send logs to logstash
        """
        async with httpx.AsyncClient(timeout=httpx.Timeout(100.0)) as client:
            r = await client.post(
                logstash_url,
                json=logs
            )
            return r