如何使用 FastAPI/Nextjs 显示 Matplotlib 图表而不在本地保存图表?

Spy*_*lis 2 python charts matplotlib next.js fastapi

我正在为网站使用 Nextjs 前端和 FastAPI 后端。我在前端有一个“以太坊地址”的输入表单,并使用输入的地址,我在后端生成一个 matplotlib 图表,显示“一段时间内的以太坊余额”。现在,我尝试使用 FastAPI 返回此图表,以便可以在前端显示它。我不想在本地保存图表。

这是到目前为止我的相关代码:

前端/ nexjs 文件名为“Chart.tsx”。正文中的“ethAddress”正在捕获输入表单中输入的数据。

fetch("http://localhost:8000/image", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(ethAddress),
    }).then(fetchEthAddresses);
Run Code Online (Sandbox Code Playgroud)

生成名为 ethBalanceTracker.py 的 matplotlib 图表的后端 python 文件

#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")
Run Code Online (Sandbox Code Playgroud)

使用名为 api.py 的 FastAPI 的后端 python 文件

@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)
Run Code Online (Sandbox Code Playgroud)

我已经尝试了上面的代码和其他一些变体。我使用是StreamingResponse因为我不想在本地保存图表。我的问题是我无法显示图表localhost:8000/images并得到一个'Internal Server Error'.

Chr*_*ris 5

您应该buf.getvalue()作为 的content传递Response,以便获取包含缓冲区全部内容的字节。StreamingResponse此外,如果整个图像数据已经加载到内存中(在本例中,在内存中的字节缓冲区中),则不应使用,而是Response直接返回 a 并设置Content-Disposition标头,以便可以将图像在浏览器中查看,如答案以及答案中所述。如果您使用Fetch API或 Axios 来获取图像,请查看此答案,了解如何在客户端显示图像。您还可以使用 FastAPI/Starlette在返回响应后关闭缓冲区,以释放内存,如此处所述。例子:BackgroundTasks

\n
import io\nimport matplotlib\nmatplotlib.use(\'AGG\')\nimport matplotlib.pyplot as plt\nfrom fastapi import FastAPI, Response, BackgroundTasks\n\napp = FastAPI()\n\ndef create_img():\n    plt.rcParams[\'figure.figsize\'] = [7.50, 3.50]\n    plt.rcParams[\'figure.autolayout\'] = True\n    fig = plt.figure()  # make sure to call this, in order to create a new figure\n    plt.plot([1, 2])\n    img_buf = io.BytesIO()\n    plt.savefig(img_buf, format=\'png\')\n    plt.close(fig)\n    return img_buf\n    \n@app.get(\'/\')\ndef get_img(background_tasks: BackgroundTasks):\n    img_buf = create_img()\n    background_tasks.add_task(img_buf.close)\n    headers = {\'Content-Disposition\': \'inline; filename="out.png"\'}\n    return Response(img_buf.getvalue(), headers=headers, media_type=\'image/png\')\n
Run Code Online (Sandbox Code Playgroud)\n

如果您想get_img()成为async def端点,则可以在外部 ThreadPool 或 PorcessPool 中执行同步函数,这样事件循环就不会被阻塞。 create_img()请查看此答案,其中提供了有关如何执行此操作的详细信息和示例。

\n

另外,如果您在使用时收到以下警告matplotlib

\n
UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.\nWARNING: QApplication was not created in the main() thread.\n
Run Code Online (Sandbox Code Playgroud)\n

这是因为它matplotlib不是线程安全的,并且大多数 GUI 后端需要从主线程运行(这实际上是来自 Qt 库本身的警告)。为了避免收到该警告,您可以简单地切换到非 GUI 后端\xe2\x80\x94,因为您甚至不需要后端,因为您在客户端的用户浏览器中显示图像\xe2\ x80\x94using ,如后端文档和上面的示例matplotlib.use()中所示(注意:必须在导入之前使用)。上面的示例中使用的是一个将图形渲染为 PNG 的后端。matplotlib.use()pyplotAGG

\n