Pydantic - how to skip validation of other fields if one of the fields fails?

Viewed 2666

Let's say I have a simple pydantic.BaseModel like this:

from myapp import User
from pydantic import BaseModel, validator

class ChangePasswordRequest(BaseModel):
    class Config:
        arbitrary_types_allowed = True  # because I'm using my `User` arbitrary type

    user: User
    current_password: constr(min_length=1, max_length=255)
    new_password: constr(min_length=8, max_length=64)
    
    @validator("user")
    def user_is_active(cls, user: User):
        assert not user.inactive, "User is not active."
        return user

    @validator("current_password")
    def current_password_validator(cls, password: str, values: Dict[str, Any]):
        user: User = values["user"]
        assert user.check_password(password), "current_password is not valid."
        return password

This is the model I'd like to use to handle user's password change. When I create ChangePasswordRequest with inactive user, I get a KeyError, because pydatinc validates all the fields.

from myapp import User

user = User(inactive=True)
r = ChangePasswordRequest(user=user, current_password="current", new_password="new-password")

# I get
KeyError: 'user'

The reason why it happens:

  • user_is_active is called and validation fails. values["user"] is not set as an effect
  • current_password_validator is called. values["user"] key does not exist and KeyError is raised.

What I want to achieve is to skip all validation if user field validation fails as there is no point of further validation. This is not a problem for a small model like mine as I can add an if statement in each validator, but this gets annoying as model grows.


@validator("current_password")
def current_password_validator(cls, password: str, values: Dict[str, Any]):
    if not user := values.get("user"):
        return password
    assert user.check_password(password), "current_password is not valid."
    return password

Other way would be to use root_validator(pre=True), but there is another problem with this approach. Error is located in the __root__ field, instead of user field.

@root_validator(pre=True)
def user_is_active(cls, values: Dict[str, Any]):
    user: Union[User, None] = values.get("user", None)
    assert getattr(user, "is_active", False), "User is not active."
    return values

# which produces following error:
[{'loc': ['__root__'],
  'msg': 'User is not active.',
  'type': 'assertion_error'}]

Does pydantic have any feature that will stop further validation if one of the "main" or "crucial" fields fails?

1 Answers

Instead of trying to stop execution after the first validator, you can begin the second one with something like this:

if "user" not in values:
    raise ValueError("Could not validate current_password because user did not pass validation")
Related