Option 1
A solution would be the following. Define lang
as Query
paramter and add a regular expression that the parameter should match. In your case, that would be ^(fr|en)$
, meaning that only fr
or en
would be valid inputs. Thus, if no match was found, the request would stop there and the client would receive a "string does not match regex..." error.
Next, define the body
parameter as a generic type of dict
and declare it as Body
field; thus, instructing FastAPI to expect a JSON
body.
之后,创立了您的<代码>models的字典。 您可使用lang
属性查询模型。 一旦找到相应的<代码>model,try
to parse the JSON
body using models[lang].parse_obj(one)
(equivalent to using models[lang](**one)
)。 如果没有<代码>ValidationError,你知道由此产生的<代码>model例有效。 否则,将。
如果您也希望<代码>FR和EN
成为有效lang
的数值,则通过<>^(i)(代码>fren)$加以调整,以确保在研究模型时将lang<>>>/code>改为较低(i.e.,models[lang.down()].parse_obj(本人)
。
import pydantic
from fastapi import FastAPI, Response, status, Body, Query
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
models = {"fr": DatasetFR, "en": DatasetEN}
@router.post("/", response_description="Add a dataset")
async def create_dataset(body: dict = Body(...), lang: str = Query(..., regex="^(fr|en)$")):
try:
model = models[lang].parse_obj(body)
except pydantic.ValidationError as e:
return Response(content=e.json(), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, media_type="application/json")
return JSONResponse(content=jsonable_encoder(dict(model)), status_code=status.HTTP_201_CREATED)
Update
Since the two models have identical attributes (i.e., title
and description
), you could define a parent model (e.g., Dataset
) with those two attributes, and have DatasetFR
and DatasetEN
models inherit those.
class Dataset(BaseModel):
title:str
description: str
class DatasetFR(Dataset):
category: CategoryFR
tags: Optional[List[TagsFR]]
class DatasetEN(Dataset):
category: CategoryEN
tags: Optional[List[TagsEN]]
Additionally, it might be a better approach to move the logic from inside the route to a dependency function and have it return the model
, if it passes the validation; otherwise, raise an HTTPException
, as also demonstrated by @tiangolo. You can use jsonable_encoder
, which is internally used by FastAPI, to encode the validation errors()
(the same function can also be used when returning the JSONResponse
).
from fastapi.exceptions import HTTPException
from fastapi import Depends
models = {"fr": DatasetFR, "en": DatasetEN}
async def checker(body: dict = Body(...), lang: str = Query(..., regex="^(fr|en)$")):
try:
model = models[lang].parse_obj(body)
except pydantic.ValidationError as e:
raise HTTPException(detail=jsonable_encoder(e.errors()), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
return model
@router.post("/", response_description="Add a dataset")
async def create_dataset(model: Dataset = Depends(checker)):
return JSONResponse(content=jsonable_encoder(dict(model)), status_code=status.HTTP_201_CREATED)
Option 2
另一种做法是,采用单一Pydantic模型(Dataset
),并定制validators > 编码>和。 您也可将<条码>-<>/条码>界定为<条码>的一部分,因此无需将其列为查询参数。 如https://stackoverflow.com/a/43634746>,,请使用<条码>。 Enum 类别,这样,如果在<代码>上存在价值,你就可以有效地核对。 Enum;并且有密码,可使用<条码><>/条码>迅速研究<条码>。 在<代码>tags的情况下,核实清单中的每一要素均为有效,使用.isset.subset
,描述为,,其中将被抓获并用于填满>>>(见“说明”一节lang<>/code>,则作为有效投入书写的代码,可调整<代码>regex/code>。
P.S. You don t even need to use Enum
with this approach. Instead, populate each set
below with the permitted values. For instance,
categories_FR = {"Eau"} categories_EN = {"Water"} tags_FR = {"eau", "pesticides"} tags_EN = {"water", "pesticides"}
. Additionally, if you would like not to use regex, but rather have a custom validation error for lang
attribute as well, you could add it in the same validator
decorator and perform validation similar (and previous) to the other two fields.
from pydantic import validator
categories_FR = set(item.value for item in CategoryFR)
categories_EN = set(item.value for item in CategoryEN)
tags_FR = set(item.value for item in TagsFR)
tags_EN = set(item.value for item in TagsEN)
cats = {"fr": categories_FR, "en": categories_EN}
tags = {"fr": tags_FR, "en": tags_EN}
def raise_error(values):
raise ValueError(f value is not a valid enumeration member; permitted: {values} )
class Dataset(BaseModel):
lang: str = Body(..., regex="^(fr|en)$")
title: str
description: str
category: str
tags: List[str]
@validator("category", "tags")
def validate_atts(cls, v, values, field):
lang = values.get( lang )
if lang:
if field.name == "category":
if v not in cats[lang]: raise_error(cats[lang])
elif field.name == "tags":
if not set(v).issubset(tags[lang]): raise_error(tags[lang])
return v
@router.post("/", response_description="Add a dataset")
async def create_dataset(model: Dataset):
return JSONResponse(content=jsonable_encoder(dict(model)), status_code=status.HTTP_201_CREATED)
Update
请注意,在Pydantic V2中,@validator
已被折旧,取而代之的是@field_validator
。 请查阅,以便了解更多细节和实例。
Option 3
另一种做法是使用Discriminated Unions,详见。
根据文件:
When Union
is used with multiple submodels, you sometimes know
exactly which submodel needs to be checked and validated and want to
enforce this. To do that you can set the same field - let s call it
my_discriminator
- in each of the submodels with a discriminated
value, which is one (or many) Literal
value(s). For your Union
,
you can set the discriminator in its value:
Field(discriminator= my_discriminator )
.
成立受歧视的工会具有许多好处:
- validation is faster since it is only attempted against one model
- only one explicit error is raised in case of failure
- the generated JSON schema implements the associated OpenAPI specification