简体   繁体   English

如何在 FastAPI 主体验证中使用可区分的联合类型? (模型上的联盟)

[英]How to use discriminated union types in FastAPI body validation? (Union on models)

I know a concept from Typescript called Discriminated unions .我从 Typescript 知道一个叫做Discriminated unions的概念。 That's a thing where you put 2 struct (classes, etc) and the type is decided depending on a struct's values.那是你放置 2 个结构(类等)的东西,类型取决于结构的值。 I'm trying to achieve similar thing in FastAPI with Pydantic validation.我正在尝试通过Pydantic验证在FastAPI中实现类似的事情。 There are two different request payloads that I can recieve.我可以收到两种不同的请求负载。 Whether it's one or another depends on accountType variable.它是一个还是另一个取决于accountType变量。 If it's creative it should be validated by RegistrationPayloadCreative and if it's brand it should validate by RegistrationPayloadBrand .如果是creative ,则应通过RegistrationPayloadCreative进行验证,如果是brand ,则应通过RegistrationPayloadBrand进行验证。 How do I achieve this?我如何实现这一目标? Couldn't find any other solution.找不到任何其他解决方案。

The problem is that it either returns问题是它要么返回

unexpected value; permitted: 'creative' (type=value_error.const; given=brand; permitted=('creative',))

Or it doesn't work at all.或者它根本不起作用。

class RegistrationPayloadBase(BaseModel):
    first_name: str
    last_name: str
    email: str
    password: str


class RegistrationPayloadCreative(RegistrationPayloadBase):
    accountType: Literal['creative']


class RegistrationPayloadBrand(RegistrationPayloadBase):
    company: str
    phone: str
    vat: str
    accountType: Literal['brand']

class A(BaseModel):
    b: Union[RegistrationPayloadBrand, RegistrationPayloadCreative]

def main():
    A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'brand'})

if __name__ == '__main__':
    main()

You should use __root__ and parse_obj instead.您应该改用__root__parse_obj

from typing import Union

from pydantic import BaseModel


class PlanetItem(BaseModel):
    id: str
    planet_name: str 
    # ...

class CarItem(BaseModel):
    id: str
    name: str
    # ...

class EitherItem(BaseModel):
    __root__: Union[PlanetItem, CarItem]



@app.get("/items/{item_id}", response_model=EitherItem)
def get_items(item_id):
    return EitherItem.parse_obj(response) # Now you get either PlanetItem or CarItem

Credit: https://github.com/tiangolo/fastapi/issues/2279#issuecomment-787517707学分: https://github.com/tiangolo/fastapi/issues/2279#issuecomment-787517707

The error message is a bit misleading since the problem is that company/phone/vat fields are mandatory in RegistrationPayloadBrand.错误消息有点误导,因为问题是公司/电话/增值税字段在 RegistrationPayloadBrand 中是强制性的。

So:所以:

 >>> A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'brand', 'company':'foo','vat':'bar', 'phone':'baz'}) 
A(b=RegistrationPayloadBrand(first_name='sdf', last_name='sdf',
email='sdf', password='sdfds', company='foo', phone='baz', vat='bar', accountType='brand'))

>>> A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'creative'})
A(b=RegistrationPayloadCreative(first_name='sdf', last_name='sdf', email='sdf', password='sdfds', accountType='creative'))

Or making them Optional (if payload not necessarily contains those fields)或者使它们可选(如果有效载荷不一定包含这些字段)

class RegistrationPayloadBrand(RegistrationPayloadBase):
    company: Optional[str]
    phone:   Optional[str]
    vat:     Optional[str]
    accountType: Literal['brand']

>>> A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'brand'})
A(b=RegistrationPayloadBrand(first_name='sdf', last_name='sdf', email='sdf', password='sdfds', company=None, phone=None, vat=None, accountType='brand'))

>>> A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'creative'})
A(b=RegistrationPayloadCreative(first_name='sdf', last_name='sdf', email='sdf', password='sdfds', accountType='creative'))


will solve the problem会解决问题

I know a concept from Typescript called Discriminated unions .我从 Typescript 中知道一个名为Discriminate unions的概念。 That's a thing where you put 2 struct (classes, etc) and the type is decided depending on a struct's values.这是您放置 2 个结构(类等)的地方,类型取决于结构的值。 I'm trying to achieve similar thing in FastAPI with Pydantic validation.我试图通过Pydantic验证在FastAPI中实现类似的事情。 There are two different request payloads that I can recieve.我可以收到两种不同的请求有效负载。 Whether it's one or another depends on accountType variable.是一个还是另一个取决于accountType变量。 If it's creative it should be validated by RegistrationPayloadCreative and if it's brand it should validate by RegistrationPayloadBrand .如果是creative ,则应由RegistrationPayloadCreative验证,如果是brand ,则应由RegistrationPayloadBrand验证。 How do I achieve this?我如何实现这一目标? Couldn't find any other solution.找不到任何其他解决方案。

The problem is that it either returns问题是它要么返回

unexpected value; permitted: 'creative' (type=value_error.const; given=brand; permitted=('creative',))

Or it doesn't work at all.或者它根本不起作用。

class RegistrationPayloadBase(BaseModel):
    first_name: str
    last_name: str
    email: str
    password: str


class RegistrationPayloadCreative(RegistrationPayloadBase):
    accountType: Literal['creative']


class RegistrationPayloadBrand(RegistrationPayloadBase):
    company: str
    phone: str
    vat: str
    accountType: Literal['brand']

class A(BaseModel):
    b: Union[RegistrationPayloadBrand, RegistrationPayloadCreative]

def main():
    A(b={'first_name': 'sdf', 'last_name': 'sdf', 'email': 'sdf', 'password': 'sdfds', 'accountType': 'brand'})

if __name__ == '__main__':
    main()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM