'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 '[email protected];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 '[email protected];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 '[email protected];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 '[email protected];type=image/jpeg' \
-F 'model={
"model_name": "detr",
"tags": [
"balloon", "dog"
]
}'
which fails with a "POST /detr2 HTTP/1.1" 422 Unprocessable Entity
error
Solution 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |