代码之家  ›  专栏  ›  技术社区  ›  HeteroChromia

zip文件。BadZipFile:尝试在文件系统中保存docx文件时,文件不是zip文件

  •  0
  • HeteroChromia  · 技术社区  · 3 年前

    我使用FastAPI根据官方文档上传文件,如下所示:

    @app.post("/create_file")
    async def create_file(file: UploadFile = File(...)):
          file2store = await file.read()
          # some code to store the BytesIO(file2store) to the other database
    

    当我使用Python请求库发送请求时,如下所示:

    f = open(".../file.txt", 'rb')
    files = {"file": (f.name, f, "multipart/form-data")}
    requests.post(url="SERVER_URL/create_file", files=files)
    

    这个 file2store 变量始终为空。有时(很少看到),它可以获得文件字节,但几乎所有时间都是空的,所以我无法恢复其他数据库上的文件。

    我也试过 bytes 而不是 UploadFile ,但我得到了相同的结果。我的代码有问题吗,或者我使用FastAPI上传文件的方式有问题吗?

    0 回复  |  直到 3 年前
        1
  •  94
  •   Chris    3 年前

    首先,根据 FastAPI documentation ,您需要安装 python-multipart 如果你还没有,上传的文件会作为“表单数据”发送。例如:

    pip install python-multipart
    

    以下示例使用 .file 的属性 UploadFile 对象以获得实际的Python文件(即。, SpooledTemporaryFile ),允许您拨打 假脱机临时文件 的方法,例如 .read() .close() ,而不必 await 他们但是,使用定义端点是很重要的 def 在这种情况下,否则,如果端点定义为 async def 。在FastAPI中,正常 def 端点 is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server) .

    这个 假脱机临时文件 FastAPI/Starlette使用的 max_size 属性设置为1MB,这意味着数据将在内存中假脱机,直到文件大小超过1MB,此时数据将写入操作系统临时目录下磁盘上的临时文件。因此,如果您上传了一个大于1MB的文件,它将不会存储在内存中,并调用 file.file.read() 实际上会将数据从磁盘读取到内存中。因此,如果文件太大,无法放入服务器的RAM,您应该按块读取文件,然后一次处理一个块,如下面的“按块读取”部分所述。你也可以看看 this answer ,展示了另一种方法 分块上传一个大文件 ,使用Starlette的 .stream() 方法和 streaming-form-data 允许解析流的包 multipart/form-data 块,这大大减少了上传文件所需的时间。

    如果必须使用定义端点 异步定义 因为你可能需要 等候 对于路线内的其他协同活动,您应该使用 异步的 内容的阅读和书写,如中所示 this answer 。此外,如果您需要发送其他数据(例如 JSON 数据)以及上传文件,请查看 this answer 。我还建议你看看 this answer ,这解释了 def 异步定义 端点。

    上载单个文件

    app.py

    from fastapi import File, UploadFile
    
    @app.post("/upload")
    def upload(file: UploadFile = File(...)):
        try:
            contents = file.file.read()
            with open(file.filename, 'wb') as f:
                f.write(contents)
        except Exception:
            return {"message": "There was an error uploading the file"}
        finally:
            file.file.close()
    
        return {"message": f"Successfully uploaded {file.filename}"}
    
    分块读取文件

    如前面和中所述 this answer ,如果文件太大,无法放入内存例如,如果您有8GB的RAM,则无法加载50GB的文件(更不用说可用的RAM总是小于机器上安装的总容量,因为其他应用程序将使用部分RAM),您应该将文件分块加载到内存中,并一次处理一块数据。然而,这种方法可能需要更长的时间才能完成,这取决于您在下面的示例中选择的块大小,块大小为 1024 * 1024 字节(即1MB)。您可以根据需要调整区块大小。

    from fastapi import File, UploadFile
            
    @app.post("/upload")
    def upload(file: UploadFile = File(...)):
        try:
            with open(file.filename, 'wb') as f:
                while contents := file.file.read(1024 * 1024):
                    f.write(contents)
        except Exception:
            return {"message": "There was an error uploading the file"}
        finally:
            file.file.close()
    
        return {"message": f"Successfully uploaded {file.filename}"}
    

    另一种选择是使用 shutil.copyfileobj() ,用于复制的内容 file-like 反对另一个 像文件一样 对象(看看 this answer 也默认情况下以默认缓冲区(组块)大小为1MB的组块读取数据(即。, 1024 * 1024 字节),对于其他平台为64KB,如源代码所示 here 。您可以通过传递可选的 length 参数注:如果为负数 值时,将读取文件的全部内容 f.read() 以及 .copyfileobj() 在引擎盖下使用(如源代码中所示 here ).

    from fastapi import File, UploadFile
    import shutil
            
    @app.post("/upload")
    def upload(file: UploadFile = File(...)):
        try:
            with open(file.filename, 'wb') as f:
                shutil.copyfileobj(file.file, f)
        except Exception:
            return {"message": "There was an error uploading the file"}
        finally:
            file.file.close()
            
        return {"message": f"Successfully uploaded {file.filename}"}
    

    test.py

    import requests
    
    url = 'http://127.0.0.1:8000/upload'
    file = {'file': open('images/1.png', 'rb')}
    resp = requests.post(url=url, files=file) 
    print(resp.json())
    

    对于HTML <form> 示例,请参阅 here .

    上载多个文件(列表)

    app.py

    from fastapi import File, UploadFile
    from typing import List
    
    @app.post("/upload")
    def upload(files: List[UploadFile] = File(...)):
        for file in files:
            try:
                contents = file.file.read()
                with open(file.filename, 'wb') as f:
                    f.write(contents)
            except Exception:
                return {"message": "There was an error uploading the file(s)"}
            finally:
                file.file.close()
    
        return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}    
    
    分块读取文件

    如本答案前面所述,如果您期望一些相当大的文件,但没有足够的RAM来从头到尾容纳所有数据,则应该将文件分块加载到内存中,从而一次处理一个块的数据(注意:根据需要调整块大小,如下所示 1024 * 1024 字节)。

    from fastapi import File, UploadFile
    from typing import List
    
    @app.post("/upload")
    def upload(files: List[UploadFile] = File(...)):
        for file in files:
            try:
                with open(file.filename, 'wb') as f:
                    while contents := file.file.read(1024 * 1024):
                        f.write(contents)
            except Exception:
                return {"message": "There was an error uploading the file(s)"}
            finally:
                file.file.close()
                
        return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}   
    

    或者,使用 shutil.copyfileobj() :

    from fastapi import File, UploadFile
    from typing import List
    import shutil
    
    @app.post("/upload")
    def upload(files: List[UploadFile] = File(...)):
        for file in files:
            try:
                with open(file.filename, 'wb') as f:
                    shutil.copyfileobj(file.file, f)
            except Exception:
                return {"message": "There was an error uploading the file(s)"}
            finally:
                file.file.close()
    
        return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}  
    

    test.py

    import requests
    
    url = 'http://127.0.0.1:8000/upload'
    files = [('files', open('images/1.png', 'rb')), ('files', open('images/2.png', 'rb'))]
    resp = requests.post(url=url, files=files) 
    print(resp.json())
    

    对于HTML <表单> 示例,请参阅 在这里 .

        2
  •  5
  •   mohd sakib    5 年前
    @app.post("/create_file/")
    async def image(image: UploadFile = File(...)):
        print(image.file)
        # print('../'+os.path.isdir(os.getcwd()+"images"),"*************")
        try:
            os.mkdir("images")
            print(os.getcwd())
        except Exception as e:
            print(e) 
        file_name = os.getcwd()+"/images/"+image.filename.replace(" ", "-")
        with open(file_name,'wb+') as f:
            f.write(image.file.read())
            f.close()
       file = jsonable_encoder({"imagePath":file_name})
       new_image = await add_image(file)
       return {"filename": new_image}