Search code examples
pythonpython-3.xschemafastapipydantic

Pydantic Dependent Schema


class LocationRequest(BaseModel):
    business_unit: Optional[str] = None
    opening: Optional[int]
    max_applicant: Optional[int]
    diversity_male: Optional[int]
    diversity_female: Optional[int]


class JobRoleRequest(BaseModel):
    name: str
    description: Optional[str] = None
    work_mode: Literal["full time", "half day"]


class JobRoleCreateRequest(JobRoleRequest):

    location: List[LocationRequest]

while adding null value for integer fields in sub-schema location: List[LocationRequest] but for business_unit when i leave it is working

Need possible ways to add null value for sub-schema fields

Sample Request schema

{
    "name": "Job role",
    "description": "job role description",
    "work_mode": "Remote",
    "location": [
        {
            "business_unit": "busiess_unit of company 1",
            "opening": 10,
            "max_applicant": 4,
            "diversity_male": 2
            "diversity_female": 2,
        }
    ]
}

If I pass the location list without optional fields like max_applicant or diversity_male, it should work. However, when I try to pass it without these fields, I'm getting an error.


Solution

  • You can check pydantic's docs: Required, optional, and nullable fields

    Non required, nullable fields

    If you want to define your field as a non required nullable field, you can simply set a default value:

    max_applicant: Optional[int] = None
    

    And if don't inform the field:

    sample = {
        "name": "Job role",
        "description": "job role description",
        "work_mode": "half day",
        "location": [
            {
                "business_unit": "busiess_unit of company 1",
                "opening": 10,
                "diversity_male": 2,
                "diversity_female": 2
            }
        ]
    }
    
    print(JobRoleCreateRequest(**sample))
    # name='Job role' description='job role description' work_mode='half day' location=[LocationRequest(business_unit='busiess_unit of company 1', opening=10, max_applicant=None, diversity_male=2, diversity_female=2)]
    

    You'll get your default value for max_applicant.

    The same applies if you pass "max_applicant": null in json.

    Note that this (got from your comments) is the same:

    opening: Optional[int] = Field(0, ge=0, le=999)
    

    Optional[int] = Field(0) means that this field is optional, will have the default value of 0 if not present, but if it is passed can be both an integer ou a null.

    Required, but can be null

    It's exactly how you defined your fields. When you define a field as optional, but without giving a default value, you are saying to pydantic that this field is required when creating the model, but if the value of the field is None, that's ok:

    sample = {
        "name": "Job role",
        "description": "job role description",
        "work_mode": "half day",
        "location": [
            {
                "business_unit": "busiess_unit of company 1",
                "opening": None, # still required to inform, but can be null
                "max_applicant": None, # still required to inform, but can be null
                "diversity_male": None, # still required to inform, but can be null
                "diversity_female": None # still required to inform, but can be null
            }
        ]
    }
    
    print(JobRoleCreateRequest(**sample))
    # name='Job role' description='job role description' work_mode='half day' location=[LocationRequest(business_unit='busiess_unit of company 1', max_applicant=None, opening=None, diversity_male=None, diversity_female=None)]
    

    Optional, but non nullable

    If you want your field to be optional, but can't be null, you can define it as:

    opening: int = Field(ge=0, le=999, default=0)
    

    This way, you are telling pydantic that opening field is not required to be informed, and it will have de default value of 0 if it's not present, but if it is present, it needs to be a non nullable integer.

    Non required, nullable but with non nullable default value

    If you want your field to be optional, to be possible to pass a null value, but to have a non nullable value if null is passed, you can use field_validator:

    opening: Optional[int] = Field(0, ge=0, le=999)
    
    @field_validator("opening")
    @classmethod
    def assign_default_if_none(cls, v: Optional[int]) -> int:
        return v or 0 # return your default value here
    

    This way, if you payload has "opening": None, opening won't be null anymore, it will be assigned the default value, or 0 in this case:

    class LocationRequest(BaseModel):
        business_unit: Optional[str] = None
        max_applicant: Optional[int]
        opening: Optional[int] = Field(0, ge=0, le=999)
        diversity_male: Optional[int]
        diversity_female: Optional[int]
    
        @field_validator("opening")
        @classmethod
        def assign_default_if_none(cls, v: Optional[int]) -> int:
            return v or 0
    
    
    class JobRoleRequest(BaseModel):
        name: str
        description: Optional[str] = None
        work_mode: Literal["full time", "half day"]
    
    
    class JobRoleCreateRequest(JobRoleRequest):
    
        location: List[LocationRequest]
    
    
    sample = {
        "name": "Job role",
        "description": "job role description",
        "work_mode": "half day",
        "location": [
            {
                "business_unit": "busiess_unit of company 1",
                "opening": None,
                "max_applicant": None,
                "diversity_male": None,
                "diversity_female": None
            }
        ]
    }
    
    print(JobRoleCreateRequest(**sample))
    # name='Job role' description='job role description' work_mode='half day' location=[LocationRequest(business_unit='busiess_unit of company 1', max_applicant=None, opening=0, diversity_male=None, diversity_female=None)
    

    Obs

    Obs: In your example payload, you are passing "work_mode": "Remote" while work_mode can be only "full time" or "half day".