FastAPI - How to pass a dictionary/JSON data in HTTP multipart request? - python

I'm trying to make a POST request:
import requests
files = {'template': open('template.xlsx', 'rb')}
payload = {
'context': {
'OUT': 'csv',
'SHORT': 'short'
},
'filename': 'file.xlsx',
'content_type': 'application/excel'
}
r = requests.post('http://localhost:8000/render', files=files, data=payload)
to a FastAPI server:
from fastapi import FastAPI, UploadFile, Form
from pydantic import Json
app = FastAPI()
#app.post('/render')
def render(template: UploadFile, context: Json = Form(), filename: str = Form(...), content_type: str = Form(...)):
# processing
return "ok"
but I get this error (422 status code):
{"detail":[{"loc":["body","context"],"msg":"Invalid JSON","type":"value_error.json"}]}
As you can see, I'm trying to pass a file and request body at the same time. I guess I could fix this if converted payload['context'] into JSON. But I'd like to fix this on server side.
How can I fix the error? Maybe convert some before params passed into view or something like this?

You would need to serialise your JSON on client side, using json.dumps(), before sending the request. Additionally, you wouldn't really need to send metadata such as filename and content_type as part of your JSON payload. You can set the filename (which you can customise) and content_type explicitly in the multipart-encoded file, which you can later retrieve on server side using the UploadFile attributes. Further, the content_type (or mime type) of an .xlsx file shouldn't be application/excel, but, as described here and here, that should be application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. Finally, I would also suggest you have a look at this answer, which explains the reason that you can't have both form-data/files and JSON together, and provides solutions that would allow one to submit both, and have the JSON data validated as well—in your case, the JSON data are not validated against a Pydantic model, meaning that the client can send any type of data or ommit any required attributes.
app.py
from fastapi import FastAPI, UploadFile, Form, File
from pydantic import Json
app = FastAPI()
#app.post('/render')
def render(file: UploadFile = File(...), context: Json = Form()):
print(context, context['OUT'], file.filename, file.content_type, sep="\n")
return "ok"
test.py
import requests
import json
url = 'http://localhost:8000/render'
files = {'file': ('file.xlsx', open('template.xlsx', 'rb'), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
payload = {'context': json.dumps({'OUT': 'csv','SHORT': 'short'})}
r = requests.post(url, files=files, data=payload)
print(r.json())

Related

fastAPI POST route replying "422 Unprocessable Entity" to a request.post() (pure python minimal example) [duplicate]

I would like to pass a JSON object to a FastAPI backend. Here is what I am doing in the frontend app:
data = {'labels': labels, 'sequences': sequences}
response = requests.post(api_url, data = data)
Here is how the backend API looks like in FastAPI:
#app.post("/api/zero-shot/")
async def Zero_Shot_Classification(request: Request):
data = await request.json()
However, I am getting this error:
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
You should use the json parameter instead (which would change the Content-Type header to application/json):
payload = {'labels': labels, 'sequences': sequences}
r = requests.post(url, json=payload)
not data which is used for sending form data with the Content-Type being application/x-www-form-urlencoded by default, or multipart/form-data if files are also included in the request—unless you serialised your JSON first and manually set the Content-Type header to application/json, as described in this answer:
payload = {'labels': labels, 'sequences': sequences}
r = requests.post(url, data=json.dumps(payload), headers={'Content-Type': 'application/json'})
Also, please have a look at the documentation on how to benefit from using Pydantic models when sending JSON request bodies, as well as this answer and this answer for more options and examples on how to define an endpoint expecting JSON data.

I need to send files and data through FastAPI. I'm trying to send requests but couldn't get it to work

I need to send files and data through FastAPI. I'm trying to send requests but couldn't get it to work
for example :
server:
import uvicorn
from fastapi import FastAPI, File
from pydantic import BaseModel
app = FastAPI()
class Data(BaseModel):
test1: str = None
test2: str = None
test3: str = None
#app.post("/uploadfile/")
async def create_file(
data: Data,
image: bytes = File(...),
):
return {"postdata": len(image),
"data": data.camera,
}
if __name__ == "__main__":
uvicorn.run(app)
client:
import requests
import cv2
import json
image = cv2.imread('/marti.jpg')
data2 = cv2.imencode(".jpg", image)[1]
payload = {"test1": "value_1", "test2": "value_2", "test3": "value_3"}
files = {
'image': ('a.jpg', data2.tobytes(), 'image/jpeg', {'Expires': '0'})
}
res = requests.post("http://localhost:8000/uploadfile/",
files=files, data=payload)
print(res.content)
print(res.status_code, res.json())
the error i got:
422 Unprocessable Entity
If I remove the 'files' from the request, it works.
If I remove the 'data' from the request, it works.
What are your suggestions in the face of this situation?
You cannot. See documentation here:
You can declare multiple File and Form parameters in a path operation, but you can't also declare Body fields that you expect to receive as JSON, as the request will have the body encoded using multipart/form-data instead of application/json.
This is not a limitation of FastAPI, it's part of the HTTP protocol.
This is typically either resolved by expecting a Form as opposed to json (see this answer here), or by having two independent endpoints; one that uploads the photo and returns a matching ID, and another that leverages that ID to send the json data to.
You are getting Data in JSON body.
If you want to do it you should do:
#app.post("/uploadfile/")
async def create_file(
test1: Optional[str] = Form(None),
test2: Optional[str] = Form(None),
test3: Optional[str] = Form(None),
image: bytes = File(...),
):
# Stuff here ...
PS: [How to use pydantic model with form data]

Pass files with json payload in single requests to web2py endpoint

I wrote endpoint called foobar in default.py controller of web2py, it looks like
#service.json
def foobar(*args, **vars):
payload = request.vars.student
print(payload)
#Above payload prints [rollNo, firstName, lastName]
#Expected is {"rollNo": 6299857, "FirstName": Prasad, "LastName": Telkikar}
fileObj= request.vars.video
studentActivity = fileObj.file.read()
#Currently I am unable to do it from unit test, but it works from postman
print(f"Student's Roll number = {payload.rollNo} \n FirstName = {payload.firstName}, \n lastName = {payload.lastName}, fileSize = {fileObj.file.tell()}")
#storing studentActivity video to specific location
#Doing business logic
Now I am writing unit test for this endpoint, where i am trying to call this endpoint using requests,
import requests
import unittest
...
class TestStudentsData(unittest.TestCase):
def test_Individual_student(self):
payload = dict(rollNo=6299857, firstName="Prasad", lastName="Telkikar")
url = "http://127.0.0.1:8000/v1/foobar"
header = dict(Authorization="Bearer <Token>")
response = requests.post(url,files={'studentActivity':open("Activity.mp4", 'rb')}, data=payload, headers=headers)
self.assertEqual(response.status_code, 200)
if __name__ == '__main__':
unittest.main(verbosity=2)
Here I am unable to pass student payload as a json.
How can I pass json payload with studentActivity file using requests?
What I tried so far?
I tried both the approaches given in this SO answer.
I read requests documentation where it says "the json parameter is ignored if either data or files is passed" requests documentation
I resolved this problem by adding proper content-type to payload,
import os
...
filePath = os.getcwd()
files = {'studentActivity':open(filePath, "Activity.mp4", 'rb'),
'payload':(None, payload, 'application/json')}
#^^^^^^^^^^^^^^^^^ This was missing
response = requests.post(url,files={'studentActivity':open("Activity.mp4", 'rb')}, data=payload, headers=headers)

How to post Multipart Form Data through python aiohttp ClientSession

I am trying to asynchronously send some Multipart-Encoded Form Data as a post request, mainly a file and two other fields.
Before trying to use asyncio I was doing the process synchronously with requests-toolbelt MultipartEncoder (https://github.com/requests/toolbelt) which worked great for normal requests, but did not work when using aiohttp for async. aiohttp provides 2 multipart classes, a FormData() class and a MultipartWriter() class, neither of which have given me much success.
After some testing, it seems like the difference is that when I use the toolbelt MultipartEncoder() the request sends the data in the form section of the post request as it should. However, when using aiohttp the request is put into the body section of the request. Not sure why they are acting differently
def multipartencode() -> ClientResponse():
# Using MultipartEncoder
m = MultipartEncoder(
fields={'type': type_str,
'metadata': json.dumps(metadata),
'file': (filename, file, 'application/json')}
)
# Using FormData
data = FormData()
data.add_field('file', file, filename=filename,
content_type='multipart/form-data')
data.add_field('type', type_str, content_type='multipart/form-data')
data.add_field('metadata', json.dumps(metadata),
content_type='multipart/form-data')
# Using MultipartWriter
with MultipartWriter('multipart/form-data') as mpwriter:
part = mpwriter.append(
file, {'CONTENT-TYPE': 'multipart/form-data'})
part.set_content_disposition('form-data')
part = mpwriter.append_form([('type', type_str)])
part.set_content_disposition('form-data')
part = mpwriter.append_form([('metadata', json.dumps(metadata))])
part.set_content_disposition('form-data')
# send request with ClientSession()
resp = await session.post(url=url, data=data, headers=headers)
return resp
How can I properly format/build the multipart-encoded request to get it to send using aiohttp?

Flask Testing a put request with custom headers

Im trying to test a PUT request in my Flask app, using flasks test client.
Everything looks good to me but i keep getting 400 BAD request.
I tried the same request using POSTMAN and I get the response back.
Here is the code
from flask import Flask
app = Flask(__name__)
data = {"filename": "/Users/resources/rovi_source_mock.csv"}
headers = {'content-type': 'application/json'}
api = "http://localhost:5000/ingest"
with app.test_client() as client:
api_response = client.put(api, data=data, headers=headers)
print(api_response)
Output
Response streamed [400 BAD REQUEST]
You do need to actually encode the data to JSON:
import json
with app.test_client() as client:
api_response = client.put(api, data=json.dumps(data), headers=headers)
Setting data to a dictionary treats that as a regular form request, so each key-value pair would be encoded into application/x-www-form-urlencoded or multipart/form-data content, if you had used either content type. As it is, your data is entirely ignored instead.
I think it is simpler to just pass the data using the json parameter instead of the data parameter:
reponse = test_client.put(
api,
json=data,
)
Quoting from here:
Passing the json argument in the test client methods sets the request
data to the JSON-serialized object and sets the content type to
application/json.

Categories