【问题标题】:Using fastAPI UploadFile with libraries that accept a file-like object将 fastAPI UploadFile 与接受类文件对象的库一起使用
【发布时间】:2021-10-01 10:32:52
【问题描述】:

我想知道在使用fastAPI 上传文件时访问底层文件对象的最佳做法。

使用fastapi上传文件时,我们得到的对象是starlette.datastructures.UploadFile。 我们可以访问底层的file 属性,即tempfile.SpooledTemporaryFile。 然后我们可以访问底层的私有 _file 属性,并将其传递给需要类文件对象的库。

下面是python-docxpdftotext 的两条路由示例:

@router.post("/open-docx")
async def open_docx(upload_file: UploadFile = File(...)):
    mydoc = docx.Document(upload_file.file._file) # 
    # do something

@router.post("/open-pdf")
async def open_pdf(upload_file: UploadFile = File(...)):
    mypdf = pdftotext.PDF(upload_file.file._file)
    # do something

但是,我不喜欢访问私有 _file 属性的想法。有没有更好的方法来传递文件对象而不先保存它?

注意:要重现上面的示例,请将以下代码放入launch.py 并运行uvicorn launch:app --reload

import docx
import pdftotext
from fastapi import FastAPI, File, UploadFile


app = FastAPI()

@app.post("/open-docx")
async def open_docx(upload_file: UploadFile = File(...)):
    mydoc = docx.Document(upload_file.file._file)
    # do something
    return {"firstparagraph": mydoc.paragraphs[0].text}

@app.post("/open-pdf")
async def open_pdf(upload_file: UploadFile = File(...)):
    mypdf = pdftotext.PDF(upload_file.file._file)
    # do something
    return {"firstpage": mypdf[0]}

【问题讨论】:

标签: python fastapi temporary-files


【解决方案1】:

在此处查看 UploadFile 的文档: https://fastapi.tiangolo.com/tutorial/request-files/#uploadfile

它说它有一个名为的属性

file: A SpooledTemporaryFile (a file-like object). 
This is the actual Python file that you can pass directly to 
other functions or libraries that expect a "file-like" object.

因此您不需要访问私有 ._file 属性。 只要通过

upload_file.file

【讨论】:

    【解决方案2】:

    请注意,这是未经测试的。根据文档,您可以使用SpooledTemporaryFile 对象 访问其底层包装器:

    返回的对象是类文件对象,其 _file 属性是 io.BytesIO 或 io.TextIOWrapper 对象(取决于是否指定了二进制或文本模式)或真正的文件对象,取决于 rollover() 是否具有被调用。 这个类文件对象可以在 with 语句中使用,就像普通文件一样。

    https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile(已添加重点)。

    因此,您似乎可以这样做:

    @router.post("/open-docx")
    async def open_docx(upload_file: UploadFile = File(...)):
        with upload_file.file as f:
            mydoc = docx.Document(f)
    

    如果您不想关闭文件,请避免使用 with。

    编辑:可搜索的文件

    遗憾的是,SpooledTemporaryFiles 无法正确查找。见this question。归结为这样一个事实,即它们在某个点之后翻滚并降落在磁盘上,此时寻找更难。由于这种翻转,访问_file 属性是安全的。因此,您要么需要将它们保存到磁盘,要么将它们显式读取到虚拟(ram)文件中。

    如果您在 Linux 上,解决方法可能是将文件保存到 /dev/shm/tmp,前者在 ram 中,后者通常是 ramdisk,并让操作系统处理将大量文件交换到磁盘.

    【讨论】:

    • 我同意这应该可行。不幸的是,当我尝试此解决方案时,出现以下错误:AttributeError: 'SpooledTemporaryFile' object has no attribute 'seekable'。
    • @Gabriel 查看更新
    • 你是对的,行为因文档大小而异。根据我的实验,_file 是 io.BufferedRandom(大文档)或 io.BytesIO(小文档)。但是,它在这两种情况下都有效,因为它们都实现了可搜索的方法。所以一方面,你是对的,保存文件并重新打开它可以解决问题,但另一方面,这似乎有点不必要,因为我们可以访问 io 对象(即使你说它不安全)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-08
    • 2019-07-12
    • 1970-01-01
    相关资源
    最近更新 更多