I am using Nextjs frontend and FastAPI backend for a website. I have an input form for an 'ethereum address' on the frontend and using the inputted address, I am generating a matplotlib chart in the backend that displays 'ethereum balance over time'. Now, I am trying to return this chart using FastAPI so I can display it on the frontend. I do not want to save the chart locally.
Here is my relevant code so far:
Frontend/ nexjs file called 'Chart.tsx'. 'ethAddress' in the body is capturing data inputted in the input form.
fetch("http://localhost:8000/image", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(ethAddress),
}).then(fetchEthAddresses);
Backend python file that generates matplotlib chart called ethBalanceTracker.py
#Imports
#Logic for chart here
plt.plot(times, balances)
buf = BytesIO()
plt.savefig(buf, format="png")
buf.seek(0)
return StreamingResponse(buf, media_type="image/png")
Backend python file using FastAPI called api.py
@app.get("/image")
async def get_images() -> dict:
return {"data": images}
@app.post("/image")
async def add_image(ethAddress: dict) -> dict:
test = EthBalanceTracker.get_transactions(ethAddress["ethAddress"])
images.append(test)
I have tried the above code and a few other variants. I am using StreamingResponse
because I do not want to save the chart locally. My issue is I cannot get the chart to display in localhost:8000/images
and am getting an 'Internal Server Error'
.
CodePudding user response:
You should pass buf.getvalue()
as the content
of your Response
, in order to get the bytes containing the entire contents of the buffer. Additionally, you shouldn't be using StreamingResponse
, if the entire image data are already loaded into memory (in this case, in an in-memory bytes buffer), but rather return a Response
directly and set the Content-Disposition
header, so that the image can be viewed in the browser, as described in this answer, as well as this and this answer. If you use Fetch API or Axios to fetch the image, please have a look at this answer on how to display the image on client side. You could also use FastAPI/Starlette's BackgroundTasks
to close the buffer after returning the response, in order to release the memory, as described here. Example:
import io
import matplotlib
matplotlib.use('AGG')
import matplotlib.pyplot as plt
from fastapi import FastAPI, Response, BackgroundTasks
app = FastAPI()
def create_img():
plt.rcParams['figure.figsize'] = [7.50, 3.50]
plt.rcParams['figure.autolayout'] = True
plt.plot([1, 2])
img_buf = io.BytesIO()
plt.savefig(img_buf, format='png')
plt.close()
return img_buf
@app.get('/')
def get_img(background_tasks: BackgroundTasks):
img_buf = create_img()
background_tasks.add_task(img_buf.close)
headers = {'Content-Disposition': 'inline; filename="out.png"'}
return Response(img_buf.getvalue(), headers=headers, media_type='image/png')
On a different note, if you are gettting the following warning with using matplotlib
:
UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
WARNING: QApplication was not created in the main() thread.
this is because matplotlib
is not thread-safe, and most GUI backends require being run from the main thread (this is actually a warning from Qt library itself). To avoid getting that warning, you can simply cahnge to a non-GUI backend—since you don't even need one, as you are displaying the image in the user's browser on client side—using matplotlib.use()
, as shown in the Backends documentation and in the example above (Note: matplotlib.use()
must be used before importing pyplot
). The AGG
used in the example above is a backend that renders graphs as PNGs.