簡體   English   中英

如何在 FastAPI 中讀取 UploadFile?

[英]How can I read UploadFile in FastAPI?

我有一個 FastAPI 端點,它接收文件,將其上傳到 s3,然后對其進行處理。 一切正常,除了處理失敗並顯示此消息:

  File "/usr/local/lib/python3.9/site-packages/starlette/datastructures.py", line 441, in read
    return self.file.read(size)
  File "/usr/local/lib/python3.9/tempfile.py", line 735, in read
    return self._file.read(*args)
ValueError: I/O operation on closed file.

我的簡化代碼如下所示:

async def process(file: UploadFile):
    reader = csv.reader(iterdecode(file.file.read(), "utf-8"), dialect="excel")  # This fails!
    datarows = []
    for row in reader:
        datarows.append(row)
    return datarows

如何讀取上傳文件的內容?

更新

我設法將問題進一步隔離開來。 這是我的簡化端點:

import boto3
from loguru import logger
from botocore.exceptions import ClientError


UPLOAD = True

@router.post("/")
async def upload(file: UploadFile = File(...)):
    if UPLOAD:
        # Upload the file
        s3_client = boto3.client("s3", endpoint_url="http://localstack:4566")
        try:
            s3_client.upload_fileobj(file.file, "local", "myfile.txt")
        except ClientError as e:
            logger.error(e)
    contents = await file.read()
    return JSONResponse({"message": "Success!"})

如果UPLOAD為 True,我會收到錯誤消息。 如果不是,一切正常。 上傳后似乎 boto3 正在關閉文件。 有什么辦法可以重新打開文件嗎? 或者發送一份副本到upload_fileobj

從 FastAPI ImportFile

從 fastapi 導入文件和上傳文件:

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File(...)):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

從 FastAPIUploadFile

例如,在異步路徑操作 function 中,您可以通過以下方式獲取內容:

contents = await myfile.read()

使用您的代碼,您應該具有以下內容:

async def process(file: UploadFile = File(...)):
    content = await file.read()
    reader = csv.reader(iterdecode(content, "utf-8"), dialect="excel")
    datarows = []
    for row in reader:
        datarows.append(row)
    return datarows

根據FastAPI 的文檔,UploadFile 使用 Python 的SpooledTemporaryFile ,“存儲在 memory 中的文件達到最大大小限制,超過此限制后它將存儲在磁盤中。”。 它“與TemporaryFile完全一樣運行”,“一旦關閉就會被銷毀(包括當 object 被垃圾回收時的隱式關閉)”。 似乎,一旦讀取了文件的內容,文件就會關閉,這反過來又會導致文件被刪除。

因此,選項 1是讀取文件內容,就像您已經做的那樣(即contents = await file.read() ),然后將這些字節上傳到您的服務器,而不是文件 object (如果可能的話)。

選項 2是將文件的內容復制到NamedTemporaryFile中,與TemporaryFile不同,它“在文件系統中有一個可見的名稱”,“可用於打開文件”。 此外,通過將delete參數設置為False ,它可以在關閉后保持可訪問性 因此,允許文件在需要時重新打開。 完成后,您可以使用 remove() 或 unlink() 方法手動刪除它。 下面是一個工作示例(受此答案啟發):

import uvicorn
from fastapi import FastAPI, File, UploadFile
from tempfile import NamedTemporaryFile
import os


app = FastAPI()

@app.post("/uploadfile/")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()

    file_copy = NamedTemporaryFile(delete=False)
    try:
        file_copy.write(contents);  # copy the received file data into a new temp file. 
        file_copy.seek(0)  # move to the beginning of the file
        print(file_copy.read(10))
        
        # Here, upload the file to your S3 service

    finally:
        file_copy.close()  # Remember to close any file instances before removing the temp file
        os.unlink(file_copy.name)  # unlink (remove) the file from the system's Temp folder
    
    # print(contents)  # Handle file contents as desired
    return {"filename": file.filename}
    

if __name__ == '__main__':
    uvicorn.run(app, host='127.0.0.1', port=8000, debug=True)

更新

如果文件需要在關閉之前重新打開(當它被上傳到您的服務器時),並且您的平台不允許這樣做(如此所述),請使用以下代碼:

@app.post("/uploadfile/")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()

    file_copy = NamedTemporaryFile('wb', delete=False)
    f = None
    try:
        # The 'with' block ensures that the file closes and data are stored
        with file_copy as f:
            f.write(contents);
        
        # Here, upload the file to your S3 service
        # You can reopen the file as many times as desired. 
        f = open(file_copy.name, 'rb')
        print(f.read(10))

    finally:
        if f is not None:
            f.close() # Remember to close any file instances before removing the temp file
        os.unlink(file_copy.name)  # unlink (remove) the file from the system's Temp folder
    
    # Handle file contents as desired
    # print(contents)
    return {"filename": file.filename}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM