Search code examples
pythonvalidationpydantic

Pydantic - change data in nested model before validation


I have this small example of nested object with pydantic.

from typing import Dict
from pydantic import BaseModel, Field, ValidationError

class UserType(BaseModel):
    name: str = Field(min_length=1)
    type: str = Field(min_length=1)

class AppConfig(BaseModel):
    key1: int = Field(gt=0)
    objects: Dict[str, UserType]

try:
    data = {
        "key1": 1,
        "objects": {
            "type1": {
                "name": "Name 2",
            },
            "type2": {
                "name": "Name 1"
            }
        }
    }
    c = AppConfig(**data)
    print(c.model_dump_json())
except ValidationError as e:
    print(e)

This obviously fail because type is not set in UserType model. My goal is to somehow set each UserType.type using associated key from objects dictionary before the actual validation. Something like:

  • I pass data to model
  • Model validates main keys
  • Before validating nested objects it copies dict key as type = key inside data that is next is passed to UserType model

Is this possible somehow with pydantic?

I know I could do all this before passing data to main model but I want to know if this is possible in pydantic model.

I also tried using model_post_init() method like this:

from typing import Dict
from pydantic import BaseModel, Field, ValidationError

class UserType(BaseModel):
    name: str = Field(min_length=1)
    type: str = Field(min_length=1)

class AppConfig(BaseModel):
    key1: int = Field(gt=0)
    objects: Dict[str, UserType]

    def model_post_init(self, __context) -> None:
        values = self.dict()
        for obj_type, obj in values["objects"].items():
            print(obj, obj_type)
            obj["type"] = obj_type

try:
    data = {
        "key1": 1,
        "objects": {
            "type1": {
                "name": "Name 2",
                #"type": "t"
            },
            "type2": {
                "name": "Name 1",
                #"type": "t"
            }
        }
    }
    c = AppConfig(**data)
    print(c.model_dump_json())
except ValidationError as e:
    print(e)

But this method is executed after validation and validation failed earlier. Also tried to set some dummy type values in data payload and then override it in model_post_init() but this did not work at all and model had only original dummy values for type.


Solution

  • You can update objects data before validation using field_validator with mode=before for objects field:

    from typing import Dict
    
    from pydantic import BaseModel, Field, ValidationError, field_validator
    
    
    class UserType(BaseModel):
        name: str = Field(min_length=1)
        type: str = Field(min_length=1)
    
    class AppConfig(BaseModel):
        key1: int = Field(gt=0)
        objects: Dict[str, UserType]
    
        
        @field_validator("objects", mode="before")
        @classmethod
        def validate_objects(cls, objects):
            for obj_type, obj_dict in objects.items():
                obj_dict.update({"type": obj_type})
            return objects
    
    try:
        data = {
            "key1": 1,
            "objects": {
                "type1": {
                    "name": "Name 2",
                },
                "type2": {
                    "name": "Name 1"
                }
            }
        }
        c = AppConfig.model_validate(data)
        print(c.model_dump_json())
    except ValidationError as e:
        print(e)