How to read the request body using orjson library in FastAPI? - python

I am writing code to receive a JSON payload in FastAPI.
Here is my code:
from fastapi import FastAPI, status, Request
from fastapi.responses import ORJSONResponse
import uvicorn
import asyncio
import orjson
app = FastAPI()
#app.post("/", status_code = status.HTTP_200_OK)
async def get_data(request: Request):
param = await request.json()
return param
However, what I want is request.json() to be used with orjson instead of the default json library of Python.
Any idea how to address this problem? Please help me, thanks.

Reading request data using orjson
When calling await request.json(), FastAPI (actually Starlette) first reads the body (using the .body() method of the Request object), and then calls json.loads() (using the standard json library of Python) to return a dict/list object to you inside the endpoint (see the implementation here)—it doesn't use .dumps(), as you mentioned in the comments section, as that method is used to serialise a Python object into JSON.
Hence, to read/convert the request body using orjson instead, you can use the below (if you would like to retrieve the raw body within a def instead of async def endpoint, please have a look at this answer):
from fastapi import FastAPI, Request
import orjson
app = FastAPI()
#app.post('/')
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return 'success'
Returning response data using orjson
When returning data such as dict, list, etc, FastAPI will automatically convert that return value into JSON, using the Python standard json.dumps(), after inspecting every item inside and making sure it is serializable with JSON, using the JSON Compatible Encoder (see this answer for more details). Hence, if you would like to use the orjson library instead, you would need to send a custom Response directly, as described in this answer. Example:
from fastapi import FastAPI, Request
import orjson
app = FastAPI()
#app.post('/')
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return Response(orjson.dumps(data), media_type='application/json')
Alternatively, you can use the ORJSONResponse provided by FastAPI (still make sure you have the orjson libray installed, as well as the content that you are returning is serializable with JSON). Have a look at futher documentation here and here on how to customise and/or set ORJSONResponse as the default response class (the implementation of ORJSONResponse can be found here). Example:
from fastapi import FastAPI, Request
from fastapi.responses import ORJSONResponse
import orjson
app = FastAPI()
#app.post('/', response_class=ORJSONResponse)
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return ORJSONResponse(data)
Please make sure to have a look here, here, as well as here and here to learn about the various approaches of sending JSON data to a FastAPI backend, and how to define an endpoint to expect and validate JSON data, instead of relying on using await request.json() (which is useful when the app requires passing arbitrary JSON data, but does not perform any validation on the data).

Related

How to pass unencoded URL in FastAPI/Swagger UI via GET method?

I would like to write a FastAPI endpoint, with a Swagger page (or something similar) that will accept a non-encoded URL as input. It should preferably use GET, not POST method.
Here's an example of a GET endpoint that does require double-URL encoding.
#app.get("/get_from_dub_encoded/{double_encoded_url}")
async def get_from_dub_encoded(double_encoded_url: str):
"""
Try https%253A%252F%252Fworld.openfoodfacts.org%252Fapi%252Fv0%252Fproduct%252F7622300489434.json
"""
original_url = urllib.parse.unquote(urllib.parse.unquote(double_encoded_url))
response = requests.get(original_url)
return response.json()
Which generates a Swagger interface as below.
The following PUT request does solve my problem, but the simplicity of a GET request with a form is better for my co-workers.
class InputModel(BaseModel):
unencoded_url: AnyUrl = Field(description="An unencoded URL for an external resource", format="url")
#app.post("/unencoded-url")
def unencoded_url(inputs: InputModel):
response = requests.get(inputs.unencoded_url)
return response.json()
How can I deploy a convenient interface like that without requiring users to write the payload for a PUT request or to perform double URL encoding?
This post has some helpful related discussion, but doesn't explicitly address the FORM solution: How to pass URL as a path parameter to a FastAPI route?
You can use Form instead of query parameters as payload.
from fastapi import FastAPI, Form
import requests
app = FastAPI()
#app.post("/")
def get_url(url: str = Form()):
"""
Try https://world.openfoodfacts.org/api/v0/product/7622300489434.json
"""
response = requests.get(url)
return response.json()
Swagger interface would look like:
You'll need to install python-multipart.
Tip: Don't use async endpoint if you are using requests or any non-async library.

How to set up that FastAPI doesn't automatically follow redirect?

Is is possible to set up FastApi in such a way that a redirect response will not automatically be followed? Assume I have the following functions:
from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse
fast_api_app = FastAPI()
#router.get('/some_url')
async def redirect_function():
redirect_response = RedirectResponse('/redirect_url', status_code=303)
return redirect_response
#router.get('/redirect_url')
async def redirected_function():
response = Response(status_code=200)
return response
When I use the requests library or the FastAPI test client to access "/some_url" I always get automatically redirected to "/redirect_url" and the response's status code will be 200. My wished behavior would be that the response status code is 303 and I am responsible for the redirection.

how do I make API request after deploying uvicorn via fastapi in mylaptop

I am new to FastAPI/model deployment, I managed to created a model which can return a result using the FastAPI docs path in my local server/laptop.
My question is, how can i access this model in jupyter notebook?
Trying to relate into other API request such as Foursquare API. How do I wrap a params dictionary
Tried the following however it is not working
import requests
url = 'http://127.0.0.1:8000/predict'
params = { 'title' : ['Product Operations Software Engineer (DevOps / SRE)']}
result = requests.post(url, params)
<Response [422]>
result.text
'{"detail":[{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}'
Input
Output
My Code
from fastapi import FastAPI
import numpy as np
import dill
import pandas as pd
import uvicorn
import re
from pydantic import BaseModel
app = FastAPI()
class JobTitle(BaseModel):
title: list
#load model
with open("logisticmodel", "rb") as to_load:
logistic_model = dill.load(to_load)
#app.get("/")
def home():
return 'welcome'
#app.post("/predict")
def predict(request: JobTitle):
X = pd.Series(request.title)
result = logistic_model.predict(X)
return {'Type' : list(result) }
Your code here indicates that your data should be put inside the HTTP request body
#app.post("/predict")
def predict(request: JobTitle):
...
But when you issue the HTTP request with the requests module,
you didn't put anything to your request body.
You should use json argument so the data can be put to the right place:
result = requests.post(
url,
json={'title': ['Product Operations Software Engineer (DevOps / SRE)']}
)
You can check this for sending an HTTP request with request body using requests module,
and this for building an API with a request body.
You need to use .dict() to get the dictionary of BaseModel instance. In your case, request.dict()
Also, Define your model like this,
class JobTitle(BaseModel):
title: List[str]
This works:
>>> result = requests.post(url, json=params)
>>> result
<Response [200]>

Sending files using python 'aiohttp' produce "There was an error parsing the body"

I am trying to make two services communicate. The first API is exposed to the user.
The second is hidden and can process files. So the first can redirect requests.
I want to make of the post request asynchronus using aiohttp but i am facing this error : "There was an error parsing the body"
To recreate the error :
Lets say this is the server code
from fastapi import FastAPI
from fastapi import UploadFile, File
app = FastAPI()
#app.post("/upload")
async def transcript_file(file: UploadFile = File(...)):
pass
And this is the client code :
from fastapi import FastAPI
import aiohttp
app = FastAPI()
#app.post("/upload_client")
async def async_call():
async with aiohttp.ClientSession() as session:
headers = {'accept': '*/*',
'Content-Type': 'multipart/form-data'}
file_dict = {"file": open("any_file","rb")}
async with session.post("http://localhost:8000/upload", headers=headers, data=file_dict) as response:
return await response.json()
Description :
Run the server on port 8000 and the client on any port you like
Open the browser and open docs on the client.
Execute the post request and see the error
Environment :
aiohttp = 3.7.4
fastapi = 0.63.0
uvicorn = 0.13.4
python-multipart = 0.0.2
Python version: 3.8.8
From this answer:
If you are using one of multipart/* content types, you are actually required to specify the boundary parameter in the Content-Type header, otherwise the server (in the case of an HTTP request) will not be able to parse the payload.
You need to remove the explicit setting of the Content-Type header, the client aiohttp will add it implicitly for you, including the boundary parameter.

How to send a file (docx, doc, pdf or json) to fastapi and predict on it without UI (i.e., HTML)?

If you know how to send a file to FastAPI server and access it in /predict endpoint for prediction using my models please help me out.
I have deployed the model using /predict endpoint and done uvicorn main:app and it's deployed but the only thing is input that is a document is in my local pc so how can I sent it to FastAPI?
I have went through the documentation of FastAPI and I have found this example code there, but the challenge is that this code creates an UI for uploading file which is not what I"m looking for.
from typing import Optional
from fastapi import FastAPI
from fastapi import FastAPI, File, UploadFile
from pydantic import BaseModel
from typing import List
from fastapi.responses import HTMLResponse
app = FastAPI()
class User(BaseModel):
user_name: dict
#app.post("/files/")
async def create_files(files: List[bytes] = File(...)):
return {"file_sizes": [len(file) for file in files]}
#app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
#app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
FASTAPI CODE
This will be your endpoint.
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
#app.post("/file")
async def upload_file(file: UploadFile = File(...)):
# Do here your stuff with the file
return {"filename": file.filename}
JAVASCRIPT CODE
This is your javascript code (assuming you are using javascript for uploading the file)
form = new FormData();
form.append("file", myFile);
let response = await fetch('/file', {
method: 'POST',
body: form
});
let result = await response.json();
EDIT: Python file upload
I'm using httpx, but technically it should be fully compatible with requests.
import httpx
# Create a dict with a key that has the same name as your file parameter and the file in binary form (the "b" in "rb")
f = {'file': open('foo.png', 'rb')}
r = httpx.post("your_url/file", files=f)
You can see more configurations/examples on the official documentation of httpx at https://www.python-httpx.org/quickstart/#sending-multipart-file-uploads.
Again, I did not test the code, since I'm tight with time at the moment.
END OF EDIT
Be aware that the parameter name of the file, MUST match the one used for sending the file.
In case, there is also another answer from me on how to test it with POSTMAN. See How to send file to fastapi endpoint using postman
NOTE
I did not test the code as I don't have time right now. In case, there is also a link with a previous answer of mine that works (unless FASTAPI introduced breaking changes).

Categories