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
/auth/registration with body{
"first_name":"sdf",
"last_name":"sdf",
"password":"sdf",
"email": "[email protected]"
}
"type": "brand" attributeunexpected value; permitted: 'creative'FastAPI Version: 0.59.0
Python version: 3.8.2
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.
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.
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.