Search code examples
pythonpython-3.xpydantic-v2

pydantic collect all failed validations from field_validator


Here a few field validators using Pydantic v2, but the behavior I'm experiencing is that the field_validator will return an error immediately instead of running all field validations and returning all field errors with the record being validated. This is probably expected, but I'm wondering if it's possible to run all field_validations for a record, and return all failures together.

class Foo(BaseModel):
    name: str
    age: int

    @field_validator("name", mode="after")
    def field_length(cls, value: str):
        value_length = len(value)
        if value_length > 20:
            raise ValueError("you know the drill, less than 20!")
        return value

    @field_validator("age", mode="before")
    def cast_str_to_int(cls, value: str) -> int:
        if not value.isnumeric():
            raise ValueError("String read in does not conform to int type.")
        return int(value)

Solution

  • Yes, this is default behaviour, but you could run all validations and collect all errors by model_validate method and all_errors=True param in config of your model:

    class Foo(BaseModel):
        name: str
        age: int
        
        # HERE IS CHANGE
        model_config = ConfigDict(all_errors=True)
    
        @field_validator("name", mode="after")
        def field_length(cls, value: str):
            value_length = len(value)
            if value_length > 20:
                raise ValueError("you know the drill, less than 20!")
            return value
    
        @field_validator("age", mode="before")
        def cast_str_to_int(cls, value: str) -> int:
            if not value.isnumeric():
                raise ValueError("String read in does not conform to int type.")
            return int(value)
    
    
    try:
        Foo.model_validate({"name": "tooooooooo loooooooooooooooooooooong", "age": "a"})
    except ValidationError as e:
        print(e.errors())
    
    

    Also, you could specify all_errors=True just in your Foo.model_validate call, without changing a config:

    class Foo(BaseModel):
        name: str
        age: int
    
        @field_validator("name", mode="after")
        def field_length(cls, value: str):
            value_length = len(value)
            if value_length > 20:
                raise ValueError("you know the drill, less than 20!")
            return value
    
        @field_validator("age", mode="before")
        def cast_str_to_int(cls, value: str) -> int:
            if not value.isnumeric():
                raise ValueError("String read in does not conform to int type.")
            return int(value)
    
    
    try:
        Foo.model_validate({"name": "tooooooooo loooooooooooooooooooooong", "age": "a"}, all_errors=True)
    except ValidationError as e:
        print(e.errors())