Skip to content
Advertisement

POST request to FastAPI using Python Requests with a file and query parameters

I am using FastAPI to serve some ML models and I have a Streamlit basic UI using Python Requests module.

One of my service is getting an image through a POST request and it is working like a charm.

Server side

@app.post("/segformer")
async def segformer(file: Optional[UploadFile] = None):

curl given by {BASE_URI}/docs

curl -X 'POST' 
  'http://localhost:8080/segformer' 
  -H 'accept: application/json' 
  -H 'Content-Type: multipart/form-data' 
  -F 'file=@image1.jpg;type=image/jpeg'

Client side using Python Requests

response = requests.post(f"{BASE_URI}/segformer", files=files)

As soon as I want to add additional parameters, it gets creepy. For example:

Server side

@dataclass
class ModelInterface:
    model_name: str = "detr"
    tags: List[str] = Query(...)

@app.post("/detr")
async def detr(file: UploadFile = File(...), model: ModelInterface = Depends()):

curl — given by {BASE_URI}/docs

curl -X 'POST' 
  'http://localhost:8080/detr?model_name=detr&tags=balloon&tags=dog' 
  -H 'accept: application/json' 
  -H 'Content-Type: multipart/form-data' 
  -F 'file=@image1.jpg;type=image/jpeg'

Until that point everything works fine. Once I want to convert that call using Python Requests, I have some issues, which always end up in /detr HTTP/1.1" 400 Bad Request error.

Here is what I’ve tried:

Client side using Python Requests

headers = {
    "Content-type": "multipart/form-data",
    "accept": "application/json"
}

payload = {
    "tags": ["balloon", "dog"]
}

response = requests.post(
    f"{BASE_URI}/detr",
    headers=headers,
    json=payload,  <- also *data*  
    files=files,
)

At the end it seems that the problem narrows on how to convert this:

curl -X 'POST' 
  'http://localhost:8080/detr?tags=balloon&tags=dog' 
  -H 'accept: application/json' 
  -H 'Content-Type: multipart/form-data' 
  -F 'file=@image1.jpg;type=image/jpeg'

to a valid Python Requests call!

I also faced the issue that the following FastAPI code:

@dataclass
class ModelInterface:
    model_name: str = "detr"
    tags: List[str] = None

@app.post("/detr2")
async def detr2(file: UploadFile = File(...), model: ModelInterface = Form(...)):
...

translates to this curl command:

curl -X 'POST' 
  'http://localhost:8080/detr2' 
  -H 'accept: application/json' 
  -H 'Content-Type: multipart/form-data' 
  -F 'file=@image1.jpg;type=image/jpeg' 
  -F 'model={
  "model_name": "detr",
  "tags": [
    "balloon", "dog"
  ]
}' 

which fails with a "POST /detr2 HTTP/1.1" 422 Unprocessable Entity error

Advertisement

Answer

To pass query parameters in Python requests, you should use params key instead. Hence:

response = requests.post(url='<your_url_here>', params=payload)

Additionally, there is no need to set the Content-type in the headers, as it will automatically be added, based on the parameters you pass to requests.post(). Doing so, the request will fail, as, in addition to multipart/form-data,”Content-type must include the boundary value used to delineate the parts in the post body. Not setting the Content-Type header ensures that requests sets it to the correct value” (ref). Have a look at this and this. Also, make sure that in files you use the same key name you gave in your endpoint, i.e., file. Thus, in Python requests it should look something like the below:

files = {('file', open('my_file.txt', 'rb'))}
response = requests.post(url='<your_url_here>', files=files, params=payload)

You could also find more options as to how to send additional data along with files at this answer.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement