如何创建一个可以接受文件/表单或 JSON 主体的 FastAPI 端点?

2025-01-06 08:32:00
admin
原创
180
摘要:问题描述:我想在 FastAPI 中创建一个可以接收multipart/form-dataJSON 主体的端点。有没有办法让这样的端点接受任一类型,或者检测正在接收哪种类型的数据?解决方案 1:选项 1您可以有一个依赖函数,在其中您将检查请求标头的值Content-Type并相应地使用 Starlette 的...

问题描述:

我想在 FastAPI 中创建一个可以接收multipart/form-dataJSON 主体的端点。有没有办法让这样的端点接受任一类型,或者检测正在接收哪种类型的数据?


解决方案 1:

选项 1

您可以有一个依赖函数,在其中您将检查请求标头的值Content-Type并相应地使用 Starlette 的方法解析正文。请注意,仅仅因为请求的Content-Type标头说(例如application/jsonapplication/x-www-form-urlencodedmultipart/form-data并不总是意味着这是真的,或者传入的数据是有效的 JSON,或文件和/或表单数据。因此,try-except在解析正文时应使用块来捕获任何潜在错误。此外,您可能需要实施各种检查以确保获得正确类型的数据和您期望需要的所有字段。对于 JSON 正文,您可以创建一个BaseModel并使用 Pydantic 的parse_obj函数来验证收到的字典(类似于此答案的方法 3 )。

关于文件/表单数据,您可以直接使用Starlette的Request对象,更具体地说,request.form()使用方法来解析主体,它将返回一个FormData不可变的多字典对象(即ImmutableMultiDict),包含文件上传和文本输入。当您发送一些list输入的值form或列表时files,您可以使用多字典的getlist()方法来检索list。对于文件,这将返回一个list对象UploadFile,您可以按照与此答案和此答案相同的方式使用这些对象来循环遍历文件并检索其内容。除了使用request.form(),您还可以直接从读取请求主体stream并使用库对其进行解析streaming-form-data,如此答案中所示。

工作示例

from fastapi import FastAPI, Depends, Request, HTTPException
from starlette.datastructures import FormData
from json import JSONDecodeError

app = FastAPI()

async def get_body(request: Request):
    content_type = request.headers.get('Content-Type')
    if content_type is None:
        raise HTTPException(status_code=400, detail='No Content-Type provided!')
    elif content_type == 'application/json':
        try:
            return await request.json()
        except JSONDecodeError:
            raise HTTPException(status_code=400, detail='Invalid JSON data')
    elif (content_type == 'application/x-www-form-urlencoded' or
          content_type.startswith('multipart/form-data')):
        try:
            return await request.form()
        except Exception:
            raise HTTPException(status_code=400, detail='Invalid Form data')
    else:
        raise HTTPException(status_code=400, detail='Content-Type not supported!')

@app.post('/')
def main(body = Depends(get_body)):
    if isinstance(body, dict):  # if JSON data received
        return body
    elif isinstance(body, FormData):  # if Form/File data received
        msg = body.get('msg')
        items = body.getlist('items')
        files = body.getlist('files')  # returns a list of UploadFile objects
        if files:
            print(files[0].file.read(10))
        return msg

选项 2

另一种选择是拥有一个端点,并将文件和/或表单数据参数定义为Optional(查看此答案和此答案,了解所有可用的方法)。 客户端的请求进入端点后,您可以检查定义的参数是否传递了任何值,这意味着它们已被客户端包含在请求正文中,并且这是一个具有或的请求Content-Typeapplication/x-www-form-urlencodedmultipart/form-data注意,如果您希望接收任意文件或表单数据,则应该使用上面的选项 1)。 否则,如果每个定义的参数仍然为None(意味着客户端未在请求正文中包含任何参数),那么这可能是一个 JSON 请求,因此,继续通过尝试将请求正文解析为 JSON 来确认这一点。

工作示例

from fastapi import FastAPI, UploadFile, File, Form, Request, HTTPException
from typing import Optional, List
from json import JSONDecodeError

app = FastAPI()

@app.post('/')
async def submit(request: Request, items: Optional[List[str]] = Form(None),
                    files: Optional[List[UploadFile]] = File(None)):
    # if File(s) and/or form-data were received
    if items or files:
        filenames = None
        if files:
            filenames = [f.filename for f in files]
        return {'File(s)/form-data': {'items': items, 'filenames': filenames}}
    else:  # check if JSON data were received
        try:
            data = await request.json()
            return {'JSON': data}
        except JSONDecodeError:
            raise HTTPException(status_code=400, detail='Invalid JSON data')

选项 3

另一个选择是定义两个单独的端点;一个用于处理 JSON 请求,另一个用于处理文件/表单数据请求。使用中间件,您可以检查传入请求是否指向您希望用户发送 JSON 或文件/表单数据的路由(在下面的示例中为/路由),如果是,请检查Content-Type与上一个选项类似的选项并相应地将请求重新路由到/submitJSON/submitForm端点(您可以通过修改path中的属性来做到这一点,如此答案request.scope中所示)。这种方法的优点是,它允许您像往常一样定义端点,而不必担心如果请求中缺少必填字段或收到的数据不是预期的格式时处理错误。

工作示例

from fastapi import FastAPI, Request, Form, File, UploadFile
from fastapi.responses import JSONResponse
from typing import List, Optional
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    items: List[str]
    msg: str

@app.middleware("http")
async def some_middleware(request: Request, call_next):
    if request.url.path == '/':
        content_type = request.headers.get('Content-Type')
        if content_type is None:
            return JSONResponse(
                content={'detail': 'No Content-Type provided!'}, status_code=400)
        elif content_type == 'application/json':
            request.scope['path'] = '/submitJSON'
        elif (content_type == 'application/x-www-form-urlencoded' or
              content_type.startswith('multipart/form-data')):
            request.scope['path'] = '/submitForm'
        else:
            return JSONResponse(
                content={'detail': 'Content-Type not supported!'}, status_code=400)

    return await call_next(request)

@app.post('/')
def main():
    return

@app.post('/submitJSON')
def submit_json(item: Item):
    return item

@app.post('/submitForm')
def submit_form(msg: str = Form(...), items: List[str] = Form(...),
                    files: Optional[List[UploadFile]] = File(None)):
    return msg

选项 4

我还建议您看一下这个答案,它提供了如何在同一个请求中同时发送 JSON 主体和文件/表单数据的解决方案,这可能会让您对要解决的问题有不同的看法。例如,将各个端点的参数声明为Optional并检查哪些参数已从客户端的请求中收到,哪些参数尚未收到——以及使用 Pydantic 的model_validate_json()方法来解析传入参数的 JSON 字符串Form——可能是解决问题的另一种方法。有关更多详细信息和示例,请参阅上面链接的答案。

使用 Python 请求测试选项 1、2 和 3

测试.py

import requests

url = 'http://127.0.0.1:8000/'
files = [('files', open('a.txt', 'rb')), ('files', open('b.txt', 'rb'))]
payload ={'items': ['foo', 'bar'], 'msg': 'Hello!'}
 
# Send Form data and files
r = requests.post(url, data=payload, files=files)  
print(r.text)

# Send Form data only
r = requests.post(url, data=payload)              
print(r.text)

# Send JSON data
r = requests.post(url, json=payload)              
print(r.text)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1553  
  IPD(Integrated Product Development)流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。其中,技术评审与决策评审是IPD流程中至关重要的环节,它们既有明显的区别,又存在紧密的协同关系。深入理解这两者的区别与协同,对于企业有效实施IPD流程,提升产品开发效率与质量具有重要意义...
IPD管理流程   27  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、ClickUp、Freshdesk、GanttPRO、Planview、Smartsheet、Asana、Nifty、HubPlanner、Teamwork。在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目按时交付的关键工具。然而...
项目管理系统   22  
  建设工程项目质量关乎社会公众的生命财产安全,也影响着企业的声誉和可持续发展。高质量的建设工程不仅能为使用者提供舒适、安全的环境,还能提升城市形象,推动经济的健康发展。在实际的项目操作中,诸多因素会对工程质量产生影响,从规划设计到施工建设,再到后期的验收维护,每一个环节都至关重要。因此,探寻并运用有效的方法来提升建设工程...
工程项目管理制度   19  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用