【问题标题】:How to use discriminated union types in FastAPI body validation? (Union on models)如何在 FastAPI 正文验证中使用可区分的联合类型? (模型上的联合)
【发布时间】:2020-07-22 18:20:45
【问题描述】:

我知道 Typescript 中的一个概念,称为可区分联合。这是您放置 2 个结构(类等)的地方,类型取决于结构的值。我正在尝试通过 Pydantic 验证在 FastAPI 中实现类似的功能。我可以收到两种不同的请求有效负载。是一个还是另一个取决于accountType 变量。如果是creative,它应该由RegistrationPayloadCreative 验证,如果它是brand,它应该由RegistrationPayloadBrand 验证。我如何实现这一目标?找不到其他解决方案。

问题是它要么返回

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

或者它根本不起作用。

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()

【问题讨论】:

    标签: python validation fastapi python-typing pydantic


    【解决方案1】:

    您应该改用__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
    

    信用:https://github.com/tiangolo/fastapi/issues/2279#issuecomment-787517707

    【讨论】:

      【解决方案2】:

      错误消息有点误导,因为问题是公司/电话/增值税字段在 RegistrationPayloadBrand 中是强制性的。

      所以:

       >>> 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'))
      

      或者将它们设为可选(如果有效负载不一定包含这些字段)

      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'))
      
      
      

      解决问题

      【讨论】:

      • 你应该使用__root__。看我的回答
      猜你喜欢
      • 2020-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      • 2020-05-20
      • 2020-05-07
      • 1970-01-01
      相关资源
      最近更新 更多