繁体   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