[英]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.