如何在使用BaseModel的FastAPI中添加未知参数到POST请求中?

3
我有以下代码:
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Request(BaseModel):
    user_name: str
    age: int
    # other unknown arguments


@app.post("/home")
def write_home(request: Request):
    print(request.__dict__)
    return {
        "user_name": request.user_name,
        "age": request.age,
        # other arguments...
    }

我希望请求可以接受可选参数(例如heightweight等),但这些参数可能是未知的。
提前感谢。
我尝试直接在请求中添加它们,但未指定的参数不会被打印出来 enter image description here

请查看这里这里 - Chris
3个回答

0

拥有未知参数与 Pydantic 的意图(即类型安全的数据解析和验证)完全相反。您可以做的(也是我会做的),是定义一个字段extra(或类似字段),用于保存动态额外数据: from typing import Any

class MyRequest(BaseModel):
    user_name: str
    age: int
    extra: dict[str, Any]

那么您就知道哪些字段始终需要存在,而任何未知的内容都会放在extra字段中。


这并没有回答问题,我认为应该将其降级为评论。 - Daniil Fajnberg

0

简单解决方案

我认为最简单的解决方案是使用extra = "allow"设置来配置您的模型(默认情况下设置为extra = "ignore")。使用该设置,将任何额外的名称-值对传递给模型构造函数将动态创建具有提供的值和类型的字段在该模型实例上。

这里是一个例子:

from fastapi import FastAPI
from pydantic import BaseModel


app = FastAPI()


class Model(BaseModel):
    user_name: str
    age: int

    class Config:
        extra = "allow"


@app.post("/home")
def write_home(model: Model) -> Model:
    print(model)
    return model

现在你可以像这样POST任意的附加数据:

{
  "user_name": "string",
  "age": 0,
  "height": 3.14
}

print语句的输出是user_name='string' age=0 height=3.14,响应体与请求完全相同。


潜在风险

在我看来,这里有一个重要的警告,它不仅适用于FastAPI,还适用于Pydantic模型:

使用extra = "allow"设置时,任何字段名称都将可用。这可能会产生严重的意外后果,因为提供的名称可以覆盖模型命名空间中的现有名称,包括内部属性(例如__fields__)和预定义方法(例如dict)。

在FastAPI端点的上下文中,想象一种情况,某人POST了如下有效载荷:

{
  "user_name": "string",
  "age": 0,
  "dict": 1
}

这将一直正常工作,直到需要调用该实例的dict方法,这恰好发生在响应形成期间。

换句话说,我们的print(model)看起来可以正常工作,产生user_name='string' age=0 dict=1,但尝试从路由处理程序中返回它将使用TypeError: 'int' object is not callable导致服务器崩溃

这只是一个例子,但这应该说明为什么如果您不正确处理它,则可能很危险或至少有问题。


其他注意事项

还有一些小的注意事项需要您知道:

  • 这可能是显而易见的,但任何这些额外字段值上都不会进行验证。在通过配置(或默认)JSON解码器进行解析后,它们将原样分配给模型实例。
  • OpenAPI文档当然无法显示这些字段是作为接受请求体模式的一部分还是包含在响应模型模式中。

0

您可以使用request.json()将请求正文解析为JSON(request应该是Starlette的Request对象的实例),如此处所示(参见选项4)(参见选项1)。这样,您就可以拥有BaseModel用于必需已知参数,同时仍然能够接受以前未知的参数。

request.json()将返回一个dict对象-如果您想知道如何循环遍历字典项,请参见此处

示例

from fastapi import FastAPI, Request
from pydantic import BaseModel

app = FastAPI()


class Base(BaseModel):
    username: str
    age: int


@app.post('/')
async def main(base: Base, request: Request):
    return await request.json()

输入示例(您可以使用Swagger UI自动文档在http://127.0.0.1:8000/docs测试端点):

{
  "username": "john",
  "gender": "m",
  "age": 20,
  "height": 1.95,
  "weight": 90
}

如果您不想使用 Pydantic 的 BaseModel,仍然可以使用 request.json() 将请求体解析为 JSON,但是除非您在端点内部或依赖类/函数中执行验证检查,否则不会对所需/已知参数进行验证。如果您想这样做,请查看上面第一段中给出的链接答案,这些答案还演示了如何检查 JSON 对象的有效性并在客户端发送无效 JSON 时引发异常。在上面的示例中,FastAPI 和 Pydantic(由于使用了 BaseModel)负责进行此验证检查。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接