As per FastAPI documentation, when including Files
or Form
parameters, "you can't also declare Body
fields that you expect to receive as JSON
", as the request will have the body encoded using application/x-www-form-urlencoded
(or multipart/form-data
, if files are included) instead of application/json
. Thus, you can't have both Form
(and/or File
) data together with JSON
data. This is not a limitation of FastAPI, it's part of the HTTP
protocol. Please have a look at this answer as well.
If you removed files: List[UploadFile] = File()
parameter from your endpoint, you would see that a client's JSON request (using a valid JSON payload as request body) would go through without any errors, as the endpoint would expect an application/json
-encoded request body (since you have declared each parameter in the endpoint with Body
type), and not a multipart/form-data
-encoded request body (which would be the case, if UploadFile
/File
parameter(s) were defined—regardless of whether you defined the rest of parameters as Body
fields, they would be expected as form-data
instead; in that case, Form
type could be used as well, which is a class that inherits directly from Body
—see here). You could also confirm that using OpenAPI/Swagger UI autodocs at http://127.0.0.1:8000/docs.
As for declaring a parameter such as policyDetails: List[dict] = Body(...)
(or even policyDetails: dict
), which is essentially expecting JSON
data, you can't do that using Form
fields, or Body
fields along with File
fields (in which case, they will again be interpreted as Form
fields, as explained earlier). Hence, the error value is not a valid dict
(even though it is not that informative), when attempting to send JSON data (i.e., a dict
or list
of dict
) in a Form
field, with the request's Content-Type
header actually being set to multipart/form-data
(in your case), or even application/x-www-form-urlencoded
, if only form-data
were included.
Solution
Therefore, your data, apart from files
, could be sent as a stringified JSON
, and on server side you could have a custom pydantic class that transforms the given JSON
string into Python dictionary and validates it against the model, as described in this answer. The files
parameter should be defined separately from the model in your endpoint. Below is a working example demonstrating the aforementioned approach.
Working Example
app.py
from fastapi import FastAPI, File, UploadFile, Body, status
from pydantic import BaseModel
from typing import Optional, List
import json
app = FastAPI()
class DataModelOut(BaseModel):
message: str = None
id: str = None
input_data: dict = None
result: List[dict] = []
statusCode: int
class DataModelIn(BaseModel):
countryId: str
policyDetails: List[dict]
leaveTypeId: str
branchIds: List[str]
cityIds: List[str]
@classmethod
def __get_validators__(cls):
yield cls.validate_to_json
@classmethod
def validate_to_json(cls, value):
if isinstance(value, str):
return cls(**json.loads(value))
return value
@app.post('/', response_model=DataModelOut)
def create_policy_details(data: DataModelIn = Body(...), files: Optional[List[UploadFile]] = File(None)):
print('Files received: ', [f.filename for f in files])
return {'input_data':data, 'statusCode': status.HTTP_201_CREATED}
Test the example above
Based on this answer as well.
test.py
import requests
url = 'http://127.0.0.1:8000/'
files = [('files', open('a.txt', 'rb')), ('files', open('b.txt', 'rb'))]
data = {'data' : '{"countryId": "US", "policyDetails": [{"name":"name1","department":"d1"}], "leaveTypeId": "some_id", "branchIds": ["b1", "b2"], "cityIds": ["c1", "c2"]}'}
resp = requests.post(url=url, data=data, files=files)
print(resp.json())
or, if you prefer this way:
import requests
import json
url = 'http://127.0.0.1:8000/'
files = [('files', open('a.txt', 'rb')), ('files', open('b.txt', 'rb'))]
data_dict = {"countryId": "US", "policyDetails": [{"name":"name1","department":"d1"}], "leaveTypeId": "some_id", "branchIds": ["b1", "b2"], "cityIds": ["c1", "c2"]}
data = {'data': json.dumps(data_dict)}
resp = requests.post(url=url, data=data, files=files)
print(resp.json())
You could also test the app using OpenAPI/Swagger UI autodocs at http://127.0.0.1:8000/docs.