FastAPI用UploadFile上传到S3报expected string or bytes-like object错误如何解决
问题根因分析
- 不要访问私有属性
_file:SpooledTemporaryFile本身已经实现了类文件对象接口,完全可以直接传给upload_fileobj使用,不需要访问内部私有属性。私有属性属于Python内部实现,结构会随版本变化,访问反而会触发类型错误。 - 同步方法误用
await:如果使用的是标准同步boto3客户端,upload_fileobj是同步方法,不支持await调用,强制加await会直接触发类型异常,这是抛出「expected string or bytes-like object」的核心原因之一。 - 文件指针位置异常:拿到
UploadFile的file对象时,文件指针可能不在起始位置,读取时会拿到空内容,也可能触发异常。 - 代码笔误:生成返回URL时使用了不存在的变量
file.filename,你的参数名是fileobject,变量不存在也会触发异常。 - 边界逻辑缺失:判断文件名后缀时没有处理文件无后缀的场景,
rsplit取索引会直接抛出异常。
修复后的代码实现
以下是可直接运行的实现,不需要将文件保存到本地:
from fastapi import FastAPI, File, UploadFile, Body, HTTPException import boto3 import logging from typing import Optional # 初始化S3客户端(同步客户端示例,适配所有S3兼容存储) s3_client = boto3.client( 's3', aws_access_key_id="你的访问密钥AK", aws_secret_access_key="你的访问密钥SK", endpoint_url="如果使用第三方S3兼容存储(如阿里云OSS、DO Spaces等)请填写对应endpoint,AWS S3可省略" ) # 按自身业务需求调整配置 ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "pdf", "docx"} FILE_CONTENT_TYPES = { "png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg", "pdf": "application/pdf", "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" } BUCKET = "你的存储桶名称" OBJ_URL = "你的存储桶公网访问前缀" app = FastAPI() # 此处用同步def避免同步S3调用阻塞事件循环,要保留async路由请改用aioboto3异步客户端 @app.post("/uploadFile") def upload_file( fileobject: UploadFile = File(...), filename: Optional[str] = Body(default=None), key: str = Body(default=None), ): # 处理无后缀文件的边界场景 if "." not in fileobject.filename: raise HTTPException(status_code=400, detail="File with no extension is not allowed") extension = fileobject.filename.rsplit(".", 1)[1].lower() if key is None: raise HTTPException(status_code=400, detail="Key is missing") if filename is None: filename = fileobject.filename if extension not in ALLOWED_EXTENSIONS: raise HTTPException( status_code=400, detail=f"File of type {extension} is not allowed" ) try: # 将文件指针移到起始位置,避免读取空内容 fileobject.file.seek(0) # 直接传入fileobject.file即可,不需要转换类型,同步方法不需要加await s3_client.upload_fileobj( fileobject.file, BUCKET, f"{key}/{filename}", ExtraArgs={ "ACL": "public-read", # 标准公开读ACL,不要写public,非标准ACL不被大部分存储支持 "ContentType": FILE_CONTENT_TYPES.get(extension, "application/octet-stream") } ) object_url = f"{OBJ_URL}/{key}/{filename}" return {"file_url": object_url} except Exception as e: logging.error(f"Exception while uploading file - {e}") raise HTTPException( status_code=400, detail=f"Exception {e} while uploading the file" )
注意事项
- 如果需要保留async异步路由,请使用
aioboto3异步客户端,不要给同步boto3方法加await,会直接抛出类型错误。 - 不要访问Python对象的下划线开头私有属性,这类属性没有兼容性保障,不同版本结构差异很大,极易触发异常。
- S3的标准公开访问ACL是
public-read,非标准的public值不被大部分S3兼容存储支持,会抛出权限错误。 - 大文件上传可以配合分块上传逻辑优化性能,小文件直接用上述实现即可。
内容的提问来源于stack exchange,提问作者Ajitabh Pandey
相关产品推荐
相关产品推荐

