如何在 FastAPI 启动时下载大文件而不阻塞事件循环?

问题描述 投票:0回答:2

这是我第一次在这里发布问题,所以如果我错过了某些细节,请原谅我。如果需要,我会更新我的问题。 所以我想要的是能够在应用程序启动时下载大文件,但它应该并行发生。因为在实际的应用程序启动中不应等待文件下载完成。

我目前正在做的是

from fastapi import FastAPI


app = FastAPI()

items = {}


@app.on_event("startup")
def startup_event():
    //Download file

现在这似乎有效,但我遇到了很多严重的工作超时错误。我想知道是否有某种方法可以在应用程序启动时进行下载,但又不会使应用程序等待下载完成。

python python-3.x asynchronous fastapi downloadfileasync
2个回答
0
投票

例如,在启动时下载 10GB 文件 (https://speed.hetzner.de/10GB.bin)。

应用程序启动时,它会使用

aiohttp
触发异步下载任务,从
https://speed.hetzner.de/10GB.bin
获取文件并将其保存为 download_file。

下载以块的形式进行,此后台进程允许应用程序启动其他任务并响应传入请求,而无需等待下载完成。

import asyncio
from fastapi import FastAPI
import aiohttp

app = FastAPI()

async def download_large_file():
    async with aiohttp.ClientSession() as session:
        url = "https://speed.hetzner.de/10GB.bin"
        async with session.get(url) as response:
            if response.status == 200:
                with open('downloaded_file', 'wb') as file:
                    while True:
                        chunk = await response.content.read(1024)
                        if not chunk:
                            break
                        file.write(chunk)

@app.on_event("startup")
async def startup_event():
    loop = asyncio.get_event_loop()
    loop.create_task(download_large_file())

希望这段代码有帮助。


0
投票

此答案从之前回答的问题中得出代码和信息。因此,请查看以下答案以获取更多详细信息和解释:

  1. 如何初始化全局对象或变量并在每个 FastAPI 端点中重用它?
  2. 在 Uvicorn/FastAPI 内发出下游 Https 请求的正确方法是什么?
  3. 在 FastAPI 端点中调用并发.futures.ThreadPoolExecutor 是否危险?
  4. FastAPI python:如何在后台运行线程?
  5. FastAPI 中从在线视频 URL 返回文件/流响应
  6. FastAPI UploadFile 与 Flask 相比慢
  7. 如何使用FastAPI下载大文件?
  8. 如何在同一运行事件循环中运行另一个应用程序?

下面提供的解决方案使用

httpx
库,该库为 Python 提供了强大的 HTTP 客户端库、
async
API 并支持 HTTP/1.1 和 HTTP/2。
aiofiles
库还用于处理
asyncio
应用程序中的文件操作(例如将文件写入磁盘)。可以在此处找到用于测试解决方案的公共视频(大文件)。

解决方案1

如果您想在应用程序中重用 HTTP 客户端,请使用此解决方案。

from fastapi import FastAPI, Request
from contextlib import asynccontextmanager
import asyncio
import aiofiles
import httpx


async def download_large_file(client: httpx.AsyncClient):
    large_file_url = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
    path = 'save_to/video.mp4'
    req = client.build_request('GET', large_file_url)
    r = await client.send(req, stream=True)
    async with aiofiles.open(path, 'wb') as f:
        async for chunk in r.aiter_raw():
            await f.write(chunk)
    await r.aclose()

    
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialise the Client on startup and add it to the state
    async with httpx.AsyncClient() as client:
        asyncio.create_task(download_large_file(client))
        yield {'client': client}
        # The Client closes on shutdown


app = FastAPI(lifespan=lifespan)


@app.get('/home')
async def home():
    return 'Hello World!'

解决方案2

如果您不需要重用 HTTP 客户端,而只需要在启动时使用它,请使用此解决方案。

from fastapi import FastAPI
from contextlib import asynccontextmanager
import asyncio
import aiofiles
import httpx


async def download_large_file():
    large_file_url = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
    path = 'save_to/video.mp4'
    async with httpx.AsyncClient() as client:
        async with client.stream('GET', large_file_url) as r:
            async with aiofiles.open(path, 'wb') as f:
                async for chunk in r.aiter_raw():   
                    await f.write(chunk)


@asynccontextmanager
async def lifespan(app: FastAPI):
    asyncio.create_task(download_large_file())
    yield


app = FastAPI(lifespan=lifespan)


@app.get('/home')
async def home():
    return 'Hello World!'
© www.soinside.com 2019 - 2024. All rights reserved.