FastAPI 支持文件下载和上传

文章目录

  • 1. 文件下载处理
    • 1.1. 服务端处理
      • 1.1.1. 下载小文件
      • 1.1.2. 下载大文件(yield 支持预览的)
      • 1.1.3. 下载大文件(bytes)
      • 1.1.4. 提供静态文件服务
      • 1.1.5. 中文文件名错误
    • 1.2. 客户端处理
      • 1.2.1. 普通下载
      • 1.2.2. 分块下载
      • 1.2.3. 显示进度条下载
      • 1.2.4. 带有断点续传的下载
      • 1.2.5. 带有超时和重试的下载
      • 1.2.6. 完整的下载器实现
  • 2. 文件上传处理
    • 2.1. 服务端处理
      • 2.1.1. 上传小文件
      • 2.1.2. 上传大文件
    • 2.2. 客户端处理

参考:
https://blog.csdn.net/weixin_42502089/article/details/147689236
https://www.cnblogs.com/bitterteaer/p/17581746.html
修改下载缓冲区大小
https://ask.csdn.net/questions/8328950

1. 文件下载处理

对于文件下载,FastAPI 提供了 FileResponse 和 StreamingResponse 两种方式。 FileResponse 适合小文件,而 StreamingResponse 适合大文件,因为它可以分块返回文件内容。

1.1. 服务端处理

1.1.1. 下载小文件

使用 FileResponse 可以直接下载文件,而无需在内存中加载整个文件。

"""
fastapi + request 上传和下载功能
"""
from fastapi import FastAPI, UploadFile
from fastapi.responses import FileResponse
import uvicornapp = FastAPI()# filename 下载时设置的文件名
@app.get("/download/small/{filename}")
async def download_small_file(filename: str):print(filename)file_path = "./测试任务.pdf"return FileResponse(file_path, filename=filename, media_type="application/octet-stream")if __name__ == '__main__':uvicorn.run(app, port=8000)

保证当前目录下有名为“测试任务.pdf”的文件。
然后使用浏览器下载:
http://127.0.0.1:8000/download/small/ceshi.pdf
在这里插入图片描述

1.1.2. 下载大文件(yield 支持预览的)

使用 StreamingResponse 可以分块下载文件,这样不会占用太多服务器资源,特别适用于大文件的下载。

from fastapi.responses import StreamingResponse
from fastapi import HTTPException
@app.get("/download/big/{filename}")
async def download_big_file(filename: str):def iter_file(path: str):with open(file=path, mode="rb") as tfile:yield tfile.read()# while chunk := tfile.read(1024*1024):  # 1MB 缓冲区#     yield chunkfile_path = "./测试任务.pdf"if not os.path.exists(file_path):raise HTTPException(status_code=404, detail="File not found")# # 支持浏览器预览# return StreamingResponse(content=iter_file(path=file_path), status_code = 200,)# 直接下载return StreamingResponse(iter_file(path=file_path), media_type="application/octet-stream", headers={"Content-Disposition": f"attachment; filename={filename}"})

然后使用浏览器下载:
http://127.0.0.1:8000/download/big/ceshi_big.pdf

1.1.3. 下载大文件(bytes)

import io
@app.get("/download/bytes/{filename}")
async def download_bytes_file(filename: str):def read_bytes(path: str):content = "Error"with open(file=path, mode="rb") as tfile:content = tfile.read()# # 失败,需要转成bytes输出# return contentreturn io.BytesIO(content)file_path = "./测试任务.pdf"if not os.path.exists(file_path):raise HTTPException(status_code=404, detail="File not found")# 解决中文名错误from urllib.parse import quote# return StreamingResponse(content=read_bytes(path=file_path), media_type="application/octet-stream", #                          headers={"Content-Disposition": "attachment; filename={}".format(quote(filename))})return StreamingResponse(content=read_bytes(path=file_path), media_type="application/octet-stream", headers={"Content-Disposition": "attachment; filename={}".format(quote(filename))})

1.1.4. 提供静态文件服务

FastAPI 允许开发者使用 StaticFiles 来提供静态文件服务。这类似于传统 Web 服务器处理文件的方式。

from fastapi.staticfiles import StaticFiles# app.mount("/static", StaticFiles(directory="static", html=True), name="free")
app.mount("/static", StaticFiles(directory="fonts", html=True), name="free")

尚未测试通过。

1.1.5. 中文文件名错误

下载文件时,当传递文件名为中文时,报错。
在这里插入图片描述

    # 解决中文名错误from urllib.parse import quotereturn StreamingResponse(iter_file(path=file_path), media_type="application/octet-stream", headers={"Content-Disposition": "attachment; filename={}".format(quote(filename))})

在这里插入图片描述

1.2. 客户端处理

参考(还有进度条, 带有断点续传的下载, 带有超时和重试的下载):
https://blog.csdn.net/u013762572/article/details/145158401
批量上传下载
https://blog.csdn.net/weixin_43413871/article/details/137027968

1.2.1. 普通下载

import requests
import os"""方式1,将整个文件下载在保存到本地"""
def download_file_bytes(file_name):# 以下三个地址均可以url = "http://127.0.0.1:8000/download/small/ceshi_samll.pdf"url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"url = "http://127.0.0.1:8000/download/big/ceshi_big.pdf"# response = requests.get(url, params={"filename": "1.txt"})response = requests.get(url)# print(response.text)with open(file_name, 'wb') as file:# file.write(response.text)file.write(response.content)if __name__ == '__main__':download_file_bytes("本地测试下载文件bytes.pdf")

1.2.2. 分块下载

import requests
import os"""方式2,通过流的方式一次写入8192字节"""
def download_file_big(file_name):# 以下三个地址均可以url = "http://127.0.0.1:8000/download/small/ceshi_samll.pdf"# url = "http://127.0.0.1:8000/download/big/ceshi_big.pdf"# url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"# response = requests.get(url, params={"filename": "./测试任务.pdf"}, stream=True)response = requests.get(url, stream=True)with open(file_name, 'wb') as file:for chunk in response.iter_content(chunk_size=8192):file.write(chunk)if __name__ == '__main__':download_file_big("本地测试下载文件big.pdf")

1.2.3. 显示进度条下载

import requests
import os
from tqdm import tqdmdef download_file_tqdm(file_name):# 以下三个地址均可以# url = "http://127.0.0.1:8000/download/small/ceshi_samll.pdf"# url = "http://127.0.0.1:8000/download/big/ceshi_big.pdf"url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"response = requests.get(url, stream=True)if response.status_code == 200:file_size = int(response.headers.get('content-length', 0))# 显示进度条progress = tqdm(response.iter_content(chunk_size=8192), total=file_size,unit='B', unit_scale=True)with open(file_name, 'wb') as f:for data in progress:f.write(data)return Truereturn Falseif __name__ == '__main__':download_file_tqdm("本地测试下载文件tqdm.pdf")

运行结果:

> python.exe .\fast_client.py
1.92kB [00:00, 14.0kB/s]

1.2.4. 带有断点续传的下载

# 带有断点续传的下载
def resume_download(file_name):# 以下三个地址均可以# url = "http://127.0.0.1:8000/download/small/ceshi_samll.pdf"# url = "http://127.0.0.1:8000/download/big/ceshi_big.pdf"url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"# 获取已下载文件大小initial_pos = os.path.getsize(file_name) if os.path.exists(file_name) else 0# 设置 Headerheaders = {'Range': f'bytes={initial_pos}-'}response = requests.get(url, stream=True, headers=headers)# 追加模式打开文件mode = 'ab' if initial_pos > 0 else 'wb'with open(file_name, mode) as f:for chunk in response.iter_content(chunk_size=8192):if chunk:f.write(chunk)

尚未测试

1.2.5. 带有超时和重试的下载

# 带有超时和重试的下载
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import time
def download_with_retry(file_name, max_retries=3, timeout=30):# 以下三个地址均可以# url = "http://127.0.0.1:8000/download/small/ceshi_samll.pdf"# url = "http://127.0.0.1:8000/download/big/ceshi_big.pdf"url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"session = requests.Session()# 设置重试策略retries = Retry(total=max_retries,backoff_factor=1,status_forcelist=[500, 502, 503, 504])session.mount('http://', HTTPAdapter(max_retries=retries))session.mount('https://', HTTPAdapter(max_retries=retries))try:response = session.get(url, stream=True, timeout=timeout)with open(file_name, 'wb') as f:for chunk in response.iter_content(chunk_size=8192):if chunk:f.write(chunk)return Trueexcept Exception as e:print(f"Download failed: {str(e)}")return False

尚未测试

1.2.6. 完整的下载器实现

import requests
from tqdm import tqdm
import os
from pathlib import Path
import hashlibclass FileDownloader:def __init__(self, chunk_size=8192):self.chunk_size = chunk_sizeself.session = requests.Session()def get_file_size(self, url):response = self.session.head(url)return int(response.headers.get('content-length', 0))def get_file_hash(self, file_path):sha256_hash = hashlib.sha256()with open(file_path, "rb") as f:for byte_block in iter(lambda: f.read(4096), b""):sha256_hash.update(byte_block)return sha256_hash.hexdigest()def download(self, url, save_path, verify_hash=None):save_path = Path(save_path)# 创建目录save_path.parent.mkdir(parents=True, exist_ok=True)# 获取文件大小file_size = self.get_file_size(url)# 设置进度条progress = tqdm(total=file_size,unit='B',unit_scale=True,desc=save_path.name)try:response = self.session.get(url, stream=True)with save_path.open('wb') as f:for chunk in response.iter_content(chunk_size=self.chunk_size):if chunk:f.write(chunk)progress.update(len(chunk))progress.close()# 验证文件完整性if verify_hash:downloaded_hash = self.get_file_hash(save_path)if downloaded_hash != verify_hash:raise ValueError("File hash verification failed")return Trueexcept Exception as e:progress.close()print(f"Download failed: {str(e)}")if save_path.exists():save_path.unlink()return Falsedef download_multiple(self, url_list, save_dir):results = []for url in url_list:filename = url.split('/')[-1]save_path = Path(save_dir) / filenamesuccess = self.download(url, save_path)results.append({'url': url,'success': success,'save_path': str(save_path)})return results# 使用示例
downloader = FileDownloader()# 单文件下载
url = "http://127.0.0.1:8000/download/bytes/ceshi_bytes.pdf"
downloader.download(url, save_path="downloads/file.pdf")# # 多文件下载
# urls = [
#     "https://example.com/file1.pdf",
#     "https://example.com/file2.pdf"
# ]
# results = downloader.download_multiple(urls, "downloads")

运行结果:

> python.exe .\fast_client_plus.py
file.pdf: 9.18MB [00:00, 60.2MB/s]

2. 文件上传处理

FastAPI 提供了 File() 和 UploadFile() 两个类用于处理文件上传。 File() 适用于小文件,而 UploadFile() 则更适合处理大文件。在文件上传时,我们建议使用异步版本的函数,这样可以避免阻塞服务器。

2.1. 服务端处理

2.1.1. 上传小文件

使用 File() 时,可以通过 httpie 或 requests 库来模拟上传操作。需要注意的是,上传文件时应该使用表单而不是 JSON,这可以通过在命令中加入 -f 或 --form 参数来指定。

from fastapi import File
@app.post("/upload/small")
async def upload_small_file(small_file: bytes = File()) -> str:return f"file size: {len(small_file)}"

尚未测试

2.1.2. 上传大文件

对于大文件,建议使用 UploadFile ,因为它会在服务器的磁盘上创建一个临时文件对象,而不是将整个文件加载到内存中。这样可以有效避免服务器内存溢出。

from fastapi import UploadFile
import time
import os
@app.post("/upload/big")
async def upload_big_file(big_file: UploadFile= File(...)):filename = f"{str(time.time()).replace('.','')}-{big_file.filename}"path = os.path.join("upload", filename)with open(path, "wb") as f:f.write(big_file.file.read())f.flush()return {"filename": filename,"filesize": big_file.size}

2.2. 客户端处理

import requests
import osdef upload_file_big(file_path):url = "http://127.0.0.1:8000/upload/big"with open(file_path, 'rb') as f:contents = f.read()response = requests.post(url, files={"file": (os.path.basename(file_path), contents, 'multipart/form-data')})return response.json()if __name__ == '__main__':upload_file_big(r"./example.pdf")

尚未测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/81879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

naive-ui切换主题

1、在App.vue文件中使用 <script setup lang"ts"> import Dashboard from ./views/dashboard/index.vue import { NConfigProvider, NGlobalStyle, darkTheme } from naive-ui import { useThemeStore } from "./store/theme"; // 获取存储的主题类…

Kotlin 协程 (三)

协程通信是协程之间进行数据交换和同步的关键机制。Kotlin 协程提供了多种通信方式&#xff0c;使得协程能够高效、安全地进行交互。以下是对协程通信的详细讲解&#xff0c;包括常见的通信原语、使用场景和示例代码。 1.1 Channel 定义&#xff1a;Channel 是一个消息队列&a…

使用SQLite Studio导出/导入SQL修复损坏的数据库

使用SQLite Studio导出/导入SQL修复损坏的数据库 使用Zotero时遇到了数据库损坏&#xff0c;在软件中寸步难行&#xff0c;遂尝试修复数据库。 一、SQLite Studio简介 SQLite Studio是一款专为SQLite数据库设计的免费开源工具&#xff0c;支持Windows/macOS/Linux。相较于其…

【git config --global alias | Git分支操作效率提升实践指南】

git config --global alias | Git分支操作效率提升实践指南 背景与痛点分析 在现代软件开发团队中&#xff0c;Git分支管理是日常工作的重要组成部分。特别是在规范的开发流程中&#xff0c;我们经常会遇到类似 feature/user-management、bugfix/login-issue 或 per/cny/dev …

(八)深度学习---计算机视觉基础

分类问题回归问题聚类问题各种复杂问题决策树√线性回归√K-means√神经网络√逻辑回归√岭回归密度聚类深度学习√集成学习√Lasso回归谱聚类条件随机场贝叶斯层次聚类隐马尔可夫模型支持向量机高斯混合聚类LDA主题模型 一.图像数字化表示及建模基础 二.卷积神经网络CNN基本原…

在tensorflow源码环境里,编译出独立的jni.so,避免依赖libtensorflowlite.so,从而实现apk体积最小化

需要在APP里使用tensorflow lite来运行PC端训练的model.tlite&#xff0c;又想apk的体积最小&#xff0c;尝试了如下方法&#xff1a; 1. 在gradle里配置 implementation("org.tensorflow:tensorflow-lite:2.16.1") 这样会引入tensorflow.jar&#xff0c;最终apk的…

neo4j框架:java安装教程

安装使用neo4j需要事先安装好java&#xff0c;java版本的选择是一个犯难的问题。本文总结了在安装java和使用Java过程中遇到的问题以及相应的解决方法。 Java的安装包可以在java官方网站Java Downloads | Oracle 中国进行下载 以java 8为例&#xff0c;选择最后一行的x64 compr…

[服务器备份教程] Rclone实战:自动备份数据到阿里云OSS/腾讯云COS等对象存储

更多服务器知识&#xff0c;尽在hostol.com 各位服务器的守护者们&#xff0c;咱们都知道&#xff0c;数据是数字时代的“黄金”&#xff0c;而服务器上的数据更是我们业务的命脉。可天有不测风云&#xff0c;硬盘可能会突然“寿终正寝”&#xff0c;手滑执行了“毁灭性”命令…

Nextjs App Router 开发指南

Next.js是一个用于构建全栈web应用的React框架。App Router 是 nextjs 的基于文件系统的路由器&#xff0c;它使用了React的最新特性&#xff0c;比如 Server Components, Suspense, 和 Server Functions。 术语 树(Tree): 一种用于可视化的层次结构。例如&#xff0c;包含父…

山东大学计算机图形学期末复习15——CG15

CG15 OpenGL缓冲区、读写操作以及混合&#xff08;Blending&#xff09; 一、OpenGL缓冲区概述 OpenGL中的缓冲区是用于存储像素数据的内存区域&#xff0c;主要包括以下类型&#xff1a; 颜色缓冲区&#xff08;Color Buffer&#xff09;&#xff1a;存储每个像素的颜色值…

html+css+js趣味小游戏~记忆卡片配对(附源码)

下面是一个简单的记忆卡片配对游戏的完整代码&#xff0c;使用HTML、CSS和JavaScript实现&#xff1a; html <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"wid…

⼀个并发访问量⽐较⼤的key在某个时间过期,在redis中这个时间过期什么意思

在 Redis 中&#xff0c;当提到一个键&#xff08;key&#xff09;“在这个时间过期”&#xff0c;指的是为该键设置了生存时间&#xff08;TTL, Time To Live&#xff09;或过期时间&#xff08;expiration time&#xff09;。一旦到达设定的过期时间&#xff0c;Redis 会自动…

【设计模式】- 行为型模式1

模板方法模式 定义了一个操作中的算法骨架&#xff0c;将算法的一些步骤推迟到子类&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些步骤 【主要角色】&#xff1a; 抽象类&#xff1a;给出一个算法的轮廓和骨架&#xff08;包括一个模板方法 和 若干基…

ubuntu22.04 卸载ESP-IDF

要在Ubuntu 22.04上完全卸载ESP-IDF&#xff0c;请按照以下步骤操作&#xff1a; 卸载ESP-IDF的步骤 删除ESP-IDF目录&#xff1a; # 假设ESP-IDF安装在~/esp/esp-idf目录 rm -rf ~/esp/esp-idf删除ESP-IDF工具链和下载的工具&#xff1a; rm -rf ~/.espressif从PATH中移除ESP…

SQLMesh 内置宏详解:@PIVOT等常用宏的核心用法与示例

本文系统解析 SQLMesh 的四个核心内置宏&#xff0c;涵盖行列转换的 PIVOT、精准去重的 DEDUPLICATE、灵活生成日期范围的 DATE_SPINE&#xff0c;以及动态表路径解析的 RESOLVE_TEMPLATE。通过真实案例演示参数配置与 SQL 渲染逻辑&#xff0c;并对比宏调用与传统 SQL 的差异&…

基于Springboot + vue3实现的工商局商家管理系统

项目描述 本系统包含管理员、商家两个角色。 管理员角色&#xff1a; 用户管理&#xff1a;管理系统中所有用户的信息&#xff0c;包括添加、删除和修改用户。 许可证申请管理&#xff1a;管理商家的许可证申请&#xff0c;包括搜索、修改或删除许可证申请。 许可证审批管理…

第五部分:第五节 - Express 路由与中间件进阶:厨房的分工与异常处理

随着你的 Express 应用变得越来越大&#xff0c;所有的路由和中间件都写在一个文件里会变得难以管理。这时候就需要将代码进行拆分和组织。此外&#xff0c;一个健壮的后端应用必须能够优雅地处理错误和一些常见的 Web 开发问题&#xff0c;比如跨域。 路由模块化 (express.Ro…

萌新联赛第(三)场

C题 这道题用暴力去写想都不要想&#xff0c;一定超时&#xff0c;于是我们需要优化&#xff0c;下面是思路过程&#xff1a; 如图&#xff0c;本题只需找到x的因数个数和(n-x)的因数个数&#xff0c;这两个相乘&#xff0c;得到的就是对于这个x来说组合的个数&#xff0c;且x…

【Android构建系统】如何在Camera Hal的Android.bp中选择性引用某个模块

背景描述 本篇文章是一个Android.bp中选择性引用某个模块的实例。 如果是Android.mk编译时期&#xff0c;在编译阶段通过某个条件判断是不是引用某个模块A, 是比较好实现的。Android15使用Android.bp构建后&#xff0c;要想在Android.bp中通过自定义的一个变量或者条件实现选…

【OneNET】_01_使用微信小程序通过新版OneNET平台获取STM32设备信息并进行控制

【OneNET】_01_使用微信小程序通过新版OneNET平台获取STM32设备信息并进行控制 一、 前言1.1 OntNET硬件方面: STM32F103C8T6 ESP01S教程 1.2 微信小程序方面 二、STM32代码部分修改三、微信小程序修改的部分四、小笔记&#xff08;个人杂记&#xff09;4.1 OneNETOneNET物联网…