如何将第二个Pydantic模式作为选项添加到FastAPI端点。

huangapple go评论84阅读模式
英文:

How to add second Pydantic schema into Fastapi endpoint as an option

问题

如果参数等于'all'我想将'date'字段添加到模式中
如果参数是'None'我想完全排除'data'字段
英文:

Ok. Guys I can't figure it out. Is any option to add second Pydantic schema option, or switch between 2 Pydantic schemas according of parameter in request?
What I want to archieve. If paremeter equals 'all' I would like to add 'date' field into schema.
And if parameter is 'None' I would like to exclude 'data' field at all.
With code below with no parameter at all got: "date": null

[
  {
    "unit": "Xeikon 1",
    "color": 65817763,
    "bw": 552903,
    "date": null
  },
  {
    "unit": "Xeikon 2",
    "color": 65825687,
    "bw": 552903,
    "date": null
  },
  {
    "unit": "Xeikon 3",
    "color": 65825784,
    "bw": 552903,
    "date": null
  }
]

This is part of my code

class Clicks(BaseModel):
    unit: str
    color: int
    bw: int
    date: Optional[date]
    class Config:
        orm_mode = True

@xeikon_api.get('/xeikon/clicks/', response_model=List[schema.Clicks])
async def xeikon_Clicks_data(details: Optional[str]=None) :
    """
     endpoint: list all Clicks data \n
    """
    try:
        if details == 'all':
            return db.session.query(Clicks).all()
        if details is None:
            from main import session_local
            response = session_local.query(Clicks)
            return reorganizeClicksData(response)
    except NoResultFound as n:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=n.orig.args,
            ) from n

答案1

得分: 1

根据你描述的方式,这将是两个不同的响应模式。因此,您需要为这两种响应定义两个不同的模型。

引用FastAPI文档的相关部分:

您可以声明响应为两种类型的Union,这意味着响应可以是其中的任何一种。在OpenAPI中,它将使用anyOf定义。要实现此目的,请使用标准的Python类型提示typing.Union

为了简化你的例子,这是一个可能的解决方案:

from datetime import date
from typing import Optional, Union, List

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

class Clicks(BaseModel):
    unit: str
    color: int
    bw: int

    class Config:
        orm_mode = True

class ExtendedClicks(Clicks):
    date: date

AnyClicks = Union[List[ExtendedClicks], List[Clicks]]

app = FastAPI()

@app.get("/clicks")
async def endpoint(details: Optional[str] = None) -> AnyClicks:
    # 获取点击数据:
    test_data = [
        {"unit": "foo", "color": 1, "bw": 1, "date": "2023-05-10"},
        {"unit": "bar", "color": 0, "bw": 0, "date": "2023-01-01"},
    ]
    if details == "all":
        return [ExtendedClicks.parse_obj(obj) for obj in test_data]
    if details is None:
        return [Clicks.parse_obj(obj) for obj in test_data]
    raise HTTPException(400)

类型联合中的顺序是重要的。由于ExtendedClicksClicks的子类型并且它只是忽略额外的值,如果我们在AnyClicks中切换顺序,响应将始终匹配Clicks,而您将永远看不到date字段。

此示例中使用details=all查询的响应:

[
  {
    "unit": "foo",
    "color": 1,
    "bw": 1,
    "date": "2023-05-10"
  },
  {
    "unit": "bar",
    "color": 0,
    "bw": 0,
    "date": "2023-01-01"
  }
]

没有查询参数的情况下的响应:

[
  {
    "unit": "foo",
    "color": 1,
    "bw": 1
  },
  {
    "unit": "bar",
    "color": 0,
    "bw": 0
  }
]

我仍然建议_不要_这样做,即不要使用返回类型的联合。无法通过OpenAPI模式表达响应模式取决于特定的查询参数。因此,从API设计的角度来看,我建议只为两个不同的模式使用两个单独的端点。

英文:

The way you describe it, that would be two distinct response schemas. Therefore you would have to define two distinct models for the response.

Let me quote the relevant section of the FastAPI docs:

> You can declare a response to be the Union of two types, that means, that the response would be any of the two. It will be defined in OpenAPI with anyOf. To do that, use the standard Python type hint typing.Union.

To simplify your example a bit, here is a possible solution:

from datetime import date
from typing import Optional, Union

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

class Clicks(BaseModel):
    unit: str
    color: int
    bw: int

    class Config:
        orm_mode = True

class ExtendedClicks(Clicks):
    date: date

AnyClicks = Union[list[ExtendedClicks], list[Clicks]]

app = FastAPI()

@app.get("/clicks")
async def endpoint(details: Optional[str] = None) -> AnyClicks:
    # Get the clicks data:
    test_data = [
        {"unit": "foo", "color": 1, "bw": 1, "date": "2023-05-10"},
        {"unit": "bar", "color": 0, "bw": 0, "date": "2023-01-01"},
    ]
    if details == "all":
        return [ExtendedClicks.parse_obj(obj) for obj in test_data]
    if details is None:
        return [Clicks.parse_obj(obj) for obj in test_data]
    raise HTTPException(400)

The order in the type union is important. Since ExtendedClicks is a subtype of Clicks and it simply ignores extra values, if we were to switch the order in AnyClicks, the response would always match Clicks and you would never see the date field.

The response in this example with the details=all query:

[
  {
    "unit": "foo",
    "color": 1,
    "bw": 1,
    "date": "2023-05-10"
  },
  {
    "unit": "bar",
    "color": 0,
    "bw": 0,
    "date": "2023-01-01"
  }
]

With no query parameters:

[
  {
    "unit": "foo",
    "color": 1,
    "bw": 1
  },
  {
    "unit": "bar",
    "color": 0,
    "bw": 0
  }
]

I would still recommend not doing that, i.e. not using a union of return types. There is no way to express via the OpenAPI schema that the response schema depends on specific query parameters. So all that one can see from the endpoint schema is that it may return a list of Clicks and it also may return a list of ExtendedClicks.

From an API design standpoint I would recommend just having two separate endpoints for the two distinct schemas.

huangapple
  • 本文由 发表于 2023年5月10日 19:41:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76217961.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定