Fastapi: One or another validation model base on string attribute value

Created on 21 Jul 2020  路  7Comments  路  Source: tiangolo/fastapi

First check

  • [x] I searched the FastAPI documentation, with the integrated search. - Integrated search doesn't work :(
  • [x] After submitting this, I commit to one of:

    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.

    • I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.

    • Implement a Pull Request for a confirmed bug.

Example

from fastapi import FastAPI

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


class RegistrationPayloadCreative(RegistrationPayloadBase):
    type: Literal["creative"]


class RegistrationPayloadBrand(RegistrationPayloadBase):
    company: str
    phone: str
    vat: str
    type: Literal["brand"]

app = FastAPI()

@app.post("/auth/registration")
def register(
    registration_payload: Union[RegistrationPayloadBrand, RegistrationPayloadCreative]
):
    return registration_payload

Description

  • Open the browser and call the endpoint /auth/registration with body
{
"first_name":"sdf",
"last_name":"sdf",
"password":"sdf",
"email": "[email protected]"
}
  • It returns error that there are missing fields (basically all fields from brand and missing 'type')`.
  • Call the same endpoint with added "type": "brand" attribute
  • It returns a lot of errors as well but it also returns error that 'type' cannot be 'brand', unexpected value; permitted: 'creative'

Environment

  • OS: MacOS
  • FastAPI Version: 0.59.0

  • Python version: 3.8.2

Additional context

I tried to look for similar issues for pydantic but could not really find anything else. I also use Python for the first time in a 2 years so there might be something I'm missing. Similar thing that works in Typescript is called Discriminated unions.

answered question

All 7 comments

With your definition of RegistrationPayloadBrand, the attributes company, phone, vat and type become required attributes.

So when you passed the following json, it cannot convert to RegistrationPayloadBrand because all its mandatory attributes are missing. Also it cannot be converted to RegistrationPayloadCreative since type(which is required) attribute is missing

{
"first_name":"sdf",
"last_name":"sdf",
"password":"sdf",
"email": "[email protected]"
}

If you have made the definition of the child classes as below, things should work properly

from typing import Optional
from pydantic import Field

class RegistrationPayloadBrand(RegistrationPayloadBase):
    company: Optional[str] = None
    phone: Optional[str] = None
    vat: Optional[str] = None
    type: str = Field("brand", const=True) # this is a required parameter which can take only value "brand"

class RegistrationPayloadCreative(RegistrationPayloadBase):
    type: str = Field("creative", const=True) # this is a required parameter which can take only value "creative"

Your request now has to contain type field in the JSON body.


References

Hello @girip11 , thank you for your answer. Interesting thing is that if I don't send type attribute in the request body at all, it automatically inserts it itself. This should not happen, right? As Field documentation says about const parameter...

:param const: this field is required and *must* take it's default value

Yes, using that documentation of const only I was able to arrive at that solution. We might have to check the pydantic source code to conclude on this behavior. Alternatively you can use regex parameter in the Field to get it working. Ex type: str = Field(..., regex=r"brand")

I searched the FastAPI documentation, with the integrated search. - Integrated search doesn't work :(

Try to shift + F5 or incognito window. I still don't know why it fails.

The problem is related to the Union field. It doesn't happen without it, can you check if this helps: https://fastapi.tiangolo.com/tutorial/extra-models/#union-or-anyof

Just for interested people, this works "as is" and as expected (without Union):

from fastapi import FastAPI, BackgroundTasks, Depends
from pydantic import BaseModel
from typing_extensions import Literal

class Test(BaseModel):
    id: int
    type: Literal["test"]


app = FastAPI()

@app.post("/")
def test(test: Test):
    return test

Doesn't seem to work. However I searched a bit more and it looks like discriminated unions are not supported by Pedantic

@jansedlon Regarding the issue with const parameter, did you try using Regex parameter and did it solve the problem?

Pydantic will give the errors of all the models it tried to validate your data, so it will include the errors for each of the Union models that failed. It tries each one, one by one, and uses the first one that passes.

Was this page helpful?
0 / 5 - 0 ratings