use*_*746 7 python upload file-upload starlette fastapi
我创建了一个端点,如下所示:
@app.post("/report/upload")
def create_upload_files(files: UploadFile = File(...)):
try:
with open(files.filename,'wb+') as wf:
wf.write(file.file.read())
wf.close()
except Exception as e:
return {"error": e.__str__()}
Run Code Online (Sandbox Code Playgroud)
它是用 uvicorn 启动的:
../venv/bin/uvicorn test_upload:app --host=0.0.0.0 --port=5000 --reload
Run Code Online (Sandbox Code Playgroud)
我正在执行一些测试,使用 Python 请求上传大约100 MB的文件,大约需要 128 秒:
../venv/bin/uvicorn test_upload:app --host=0.0.0.0 --port=5000 --reload
Run Code Online (Sandbox Code Playgroud)
我使用 Flask 通过 API 端点测试了相同的上传脚本,大约需要 0.5 秒:
f = open(sys.argv[1],"rb").read()
hex_convert = binascii.hexlify(f)
items = {"files": hex_convert.decode()}
start = time.time()
r = requests.post("http://192.168.0.90:5000/report/upload",files=items)
end = time.time() - start
print(end)
Run Code Online (Sandbox Code Playgroud)
我做错了什么吗?
Chr*_*ris 15
您可以在用 定义端点后使用同步写入来写入文件,如本答案def所示,或者在用 ;定义端点后使用异步写入(利用aiofiles )来写入文件。方法就是方法,因此,您需要它们。下面给出示例。有关vs的更多详细信息以及它们如何影响 API 的性能(取决于端点内执行的任务),请查看此答案。async defUploadFileasyncawaitdefasync def
应用程序.py
\nfrom fastapi import File, UploadFile\nimport aiofiles\n\n@app.post("/upload")\nasync def upload(file: UploadFile = File(...)):\n try:\n contents = await file.read()\n async with aiofiles.open(file.filename, \'wb\') as f:\n await f.write(contents)\n except Exception:\n return {"message": "There was an error uploading the file"}\n finally:\n await file.close()\n\n return {"message": f"Successfuly uploaded {file.filename}"}\nRun Code Online (Sandbox Code Playgroud)\n正如此答案中所解释的,FastAPI/Starlette 在底层使用属性设置为 1 MB 的SpooledTemporaryFilemax_size,这意味着数据将在内存中假脱机,直到文件大小超过 1 MB,此时数据将写入磁盘上的临时文件,因此,调用await file.read()实际上会将数据从磁盘读取到内存中(如果上传的文件大于 1 MB)。因此,您可能希望以分块方式使用async,以避免将整个文件加载到内存中,这可能会导致问题\xe2\x80\x94,例如,如果您有 8GB RAM,则无法加载 50GB 文件(更不用说可用的 RAM 总是小于安装的总量,因为机器上运行的本机操作系统和其他应用程序将使用一些 RAM)。因此,在这种情况下,您应该将文件分块加载到内存中,并一次处理一个数据块。但是,此方法可能需要更长的时间才能完成,具体取决于您选择的块大小;下,即1024 * 1024字节 (= 1MB)。您可以根据需要调整块大小。
from fastapi import File, UploadFile\nimport aiofiles\n\n@app.post("/upload")\nasync def upload(file: UploadFile = File(...)):\n try:\n async with aiofiles.open(file.filename, \'wb\') as f:\n while contents := await file.read(1024 * 1024):\n await f.write(contents)\n except Exception:\n return {"message": "There was an error uploading the file"}\n finally:\n await file.close()\n\n return {"message": f"Successfuly uploaded {file.filename}"}\nRun Code Online (Sandbox Code Playgroud)\n或者,您可以使用shutil.copyfileobj(),它用于将一个file-like对象的内容复制到另一个file-like对象(另请参阅此答案)。默认情况下,数据以块的形式读取,1024 * 1024Windows 的默认缓冲区(块)大小为 1MB(即字节),其他平台为 64KB(请参阅此处的源代码)。您可以通过传递可选参数来指定缓冲区大小length。注意:如果length传递负值,则将读取文件的全部内容\xe2\x80\x94,请参阅f.read()文档,该文档.copyfileobj()在幕后使用。源代码可以在这里.copyfileobj()找到\xe2\x80\x94,在读取/写入文件内容方面与以前的方法没有什么不同。但是,在幕后使用阻塞 I/O 操作,这将导致阻塞整个服务器(如果在端点内使用)。因此,为了避免这种情况,您可以使用 Starlette在单独的线程(然后等待)中运行所有需要的函数,以确保主线程(运行协程的地方)不会被阻塞。当您调用对象的方法时,FastAPI 在内部使用完全相同的函数,即 , , 。等\xe2\x80\x94请参阅此处的源代码。例子:.copyfileobj()async defrun_in_threadpool()asyncUploadFile.write().read()close()
from fastapi import File, UploadFile\nfrom fastapi.concurrency import run_in_threadpool\nimport shutil\n \n@app.post("/upload")\nasync def upload(file: UploadFile = File(...)):\n try:\n f = await run_in_threadpool(open, file.filename, \'wb\')\n await run_in_threadpool(shutil.copyfileobj, file.file, f)\n except Exception:\n return {"message": "There was an error uploading the file"}\n finally:\n if \'f\' in locals(): await run_in_threadpool(f.close)\n await file.close()\n\n return {"message": f"Successfuly uploaded {file.filename}"}\nRun Code Online (Sandbox Code Playgroud)\n测试.py
\nimport requests\n\nurl = \'http://127.0.0.1:8000/upload\'\nfile = {\'file\': open(\'images/1.png\', \'rb\')}\nr = requests.post(url=url, files=file) \nprint(r.json())\nRun Code Online (Sandbox Code Playgroud)\n有关 HTML<form>示例,请参阅此处。
应用程序.py
\nfrom fastapi import File, UploadFile\nimport aiofiles\n\n@app.post("/upload")\nasync def upload(files: List[UploadFile] = File(...)):\n for file in files:\n try:\n contents = await file.read()\n async with aiofiles.open(file.filename, \'wb\') as f:\n await f.write(contents)\n except Exception:\n return {"message": "There was an error uploading the file(s)"}\n finally:\n await file.close()\n\n return {"message": f"Successfuly uploaded {[file.filename for file in files]}"} \nRun Code Online (Sandbox Code Playgroud)\n要以块的形式读取文件,请参阅本答案前面描述的方法。
\n测试.py
\nimport requests\n\nurl = \'http://127.0.0.1:8000/upload\'\nfiles = [(\'files\', open(\'images/1.png\', \'rb\')), (\'files\', open(\'images/2.png\', \'rb\'))]\nr = requests.post(url=url, files=files) \nprint(r.json())\nRun Code Online (Sandbox Code Playgroud)\n有关 HTML<form>示例,请参阅此处。
深入研究源代码,似乎最新版本的Starlette(FastAPI 在下面使用)使用属性设置为1MB(字节)的SpooledTemporaryFile(UploadFile数据结构) - 请参阅此处- 与设置为默认值的旧版本相比value,即0字节,比如这里的。max_size1024 * 1024max_size
上述意味着,在过去,无论文件大小如何,数据都会完全加载到内存中(如果文件无法装入 RAM,这可能会导致问题),而在最新版本中,数据会被假脱机处理在内存中,直到file大小超过max_size(即 1MB),此时内容将写入磁盘;更具体地说,到操作系统的临时目录(注意:这也意味着您可以上传的文件的最大大小受到系统临时目录可用存储的限制。如果有足够的存储(满足您的需要)在您的系统上可用,无需担心;否则,请查看有关如何更改默认临时目录的答案)。这样,多次写入文件的过程\xe2\x80\x94,即首先将数据加载到RAM中,然后,如果数据大小超过1MB,则将文件写入临时目录,然后从临时目录中读取文件(使用file.read()) 最后,将文件写入永久目录 \xe2\x80\x94 与使用 Flask 框架相比,上传文件速度较慢,正如 OP 在他们的问题中指出的那样(尽管时间上的差异并不是那么大,但只是几秒钟,具体取决于文件的大小)。
解决方案(如果需要上传超过 1MB 的文件并且上传时间对他们来说很重要)将以request流的形式访问正文。根据Starlette 文档,如果您访问.stream(),则提供字节块,而不将整个主体存储到内存(如果主体包含超过 1MB 的文件数据,则稍后存储到临时目录)。下面给出了示例,其中上传时间记录在客户端,最终结果与使用 Flask 框架和 OP 问题中给出的示例相同。
应用程序.py
\nfrom fastapi import Request\nimport aiofiles\n\n@app.post(\'/upload\')\nasync def upload(request: Request):\n try:\n filename = request.headers[\'filename\']\n async with aiofiles.open(filename, \'wb\') as f:\n async for chunk in request.stream():\n await f.write(chunk)\n except Exception:\n return {"message": "There was an error uploading the file"}\n \n return {"message": f"Successfuly uploaded {filename}"}\nRun Code Online (Sandbox Code Playgroud)\n如果您的应用程序不需要将文件保存到磁盘,而您需要的只是将文件直接加载到内存中,则可以使用以下内容(确保您的 RAM 有足够的空间来容纳累积的数据):
\nfrom fastapi import Request\n\n@app.post(\'/upload\')\nasync def upload(request: Request):\n body = b\'\'\n try:\n filename = request.headers[\'filename\']\n async for chunk in request.stream():\n body += chunk\n except Exception:\n return {"message": "There was an error uploading the file"}\n \n #print(body.decode())\n return {"message": f"Successfuly uploaded {filename}"}\nRun Code Online (Sandbox Code Playgroud)\n测试.py
\nimport requests\nimport time\n\nwith open("images/1.png", "rb") as f:\n data = f.read()\n \nurl = \'http://127.0.0.1:8000/upload\'\nheaders = {\'filename\': \'1.png\'}\n\nstart = time.time()\nr = requests.post(url=url, data=data, headers=headers)\nend = time.time() - start\n\nprint(f\'Elapsed time is {end} seconds.\', \'\\n\')\nprint(r.json())\nRun Code Online (Sandbox Code Playgroud)\n如果您必须上传一个不适合您客户端 RAM 的相当大的文件(例如,您客户端设备上有 2 GB 可用 RAM 并尝试加载 4 GB 文件) ),您也应该在客户端使用流式上传,这将允许您发送大型流或文件,而无需将它们读入内存(不过,上传可能需要更多时间,具体取决于块大小,您可能会通过以块的形式读取文件并根据需要设置块大小来进行自定义)。requestsPython和中都给出了示例httpx(这可能会产生比requests)。
test.py(使用requests)
import requests\nimport time\n\nurl = \'http://127.0.0.1:8000/upload\'\nheaders = {\'filename\': \'1.png\'}\n\nstart = time.time()\n\nwith open("images/1.png", "rb") as f:\n r = requests.post(url=url, data=f, headers=headers)\n \nend = time.time() - start\n\nprint(f\'Elapsed time is {end} seconds.\', \'\\n\')\nprint(r.json())\nRun Code Online (Sandbox Code Playgroud)\ntest.py(使用httpx)
import httpx\nimport time\n\nurl = \'http://127.0.0.1:8000/upload\'\nheaders = {\'filename\': \'1.png\'}\n\nstart = time.time()\n\nwith open("images/1.png", "rb") as f:\n r = httpx.post(url=url, data=f, headers=headers)\n \nend = time.time() - start\n\nprint(f\'Elapsed time is {end} seconds.\', \'\\n\')\nprint(r.json())\nRun Code Online (Sandbox Code Playgroud)\n有关基于上述方法(即使用方法)的更多详细信息和代码示例request.stream()(关于上传多个文件和表单/JSON数据) ,请查看此答案。
| 归档时间: |
|
| 查看次数: |
9998 次 |
| 最近记录: |