How to pass File object to HTTPX request in FastAPI endpoint

Viewed 500

The idea is to get file object from one endpoint and send it to other endpoints to work with it without saving it. Let's have this expample code:

import httpx
from fastapi import Request, UploadFile, File


app = FastAPI()
client = httpx.AsyncClient()

@app.post("/endpoint/")
async def foo(request: Request, file: UploadFile = File(...))
    urls = ["/some/other/endpoint", "/another/endpoint/"]
    for url in urls:
        response = await client.post(url) # here I need to send the file to the other endpoint 
    return {"bar": "baz"}


@app.post("/some/other/endpoint/")
async def baz(request: Request, file: UploadFile = File(...)): # and here to use it
     # Do something with the file object
     return {"file": file.filename}


@app.post("/another/endpoint/")
async def baz(request: Request, file: UploadFile = File(...)): # and here to use it too
     # Do something with the file object
     return {"file": file.content_type}

As stated here I tried to do something like this:

data = {'file': file}
response = await client.post(url, data=data)

But it errored with

'{"detail":[{"loc":["body","file"],"msg":"Expected UploadFile, received: <class \'str\'>","type":"value_error"}]}'

Example curl request:

curl -X 'POST' -F 'file=@somefile' someserver/endpoint/
1 Answers

httpx similar to requests uses files=.... to send files.

ie.

post(..., files={'file': file.file}, ...)

or with filename

post(..., files={'file': (file.filename, file.file)}, ...)

BTW:

If you send the same file a few times then you may need to move pointer to the beginning of file after sending

file.file.seek(0)

or

await file.seek(0)

Full working code

from fastapi import FastAPI, Request, UploadFile, File
import httpx

app = FastAPI()
client = httpx.AsyncClient()


@app.post("/endpoint/")
async def foo(request: Request, file: UploadFile = File(...)):
    print('/endpoint/')
    
    urls = ["/some/other/endpoint/", "/another/endpoint/"]
    
    results = []
    
    for url in urls:
        response = await client.post('http://localhost:8000' + url, files={'file': (file.filename, file.file)})
        #file.file.seek(0)  # move back at the beginning of file after sending to other URL
        await file.seek(0)  # move back at the beginning of file after sending to other URL
        results.append(response)
        
    results = [item.text for item in results]
    
    print('results:', results)
    
    return {"bar": "baz"}


@app.post("/some/other/endpoint/")
async def baz(request: Request, file: UploadFile = File(...)):
    print('/some/other/endpoint/')
    
    print('filename:', file.filename)
    print('content_type:', file.content_type)
    
    # Do something with the file object
    
    return {"file": file.filename}


@app.post("/another/endpoint/")
async def baz(request: Request, file: UploadFile = File(...)): 
    print('/another/endpoint/')
    
    print('filename:', file.filename)
    print('content_type:', file.content_type)
    
    # Do something with the file object
    
    return {"file": file.content_type}
Related