Conozco un concepto de Typescript llamado Uniones discriminadas . Eso es algo en lo que pones 2 estructuras (clases, etc.) y el tipo se decide según los valores de una estructura. Estoy tratando de lograr algo similar en FastAPI con validación Pydantic . Hay dos cargas de solicitud diferentes que puedo recibir. Si es uno u otro depende de la variable accountType . Si es una creative , debe ser validada por RegistrationPayloadCreative y si es una brand , debe ser validada por RegistrationPayloadBrand . ¿Cómo logro esto? No pude encontrar ninguna otra solución.
El problema es que o vuelve
unexpected value; permitted: 'creative' (type=value_error.const; given=brand; permitted=('creative',))
O no funciona en absoluto.
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()Debería usar __root__ y parse_obj en su lugar.
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 CarItemCrédito: https://github.com/tiangolo/fastapi/issues/2279#issuecomment-787517707
El mensaje de error es un poco engañoso ya que el problema es que los campos de empresa/teléfono/IVA son obligatorios en RegistrationPayloadBrand.
Asi que:
>>> 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'))O haciéndolos opcionales (si la carga útil no necesariamente contiene esos campos)
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'))resolverá el problema