You can't raise multiple Validation errors/exceptions for a specific field, in the way you demonstrate in your question. Suggested solutions are given below.
Option 1
Concatenate error messages using a single variable, and raise the ValueError
once at the end (if errors occured):
@validator('password', always=True)
def validate_password1(cls, value):
password = value.get_secret_value()
min_length = 8
errors = ''
if len(password) < min_length:
errors += 'Password must be at least 8 characters long. '
if not any(character.islower() for character in password):
errors += 'Password should contain at least one lowercase character.'
if errors:
raise ValueError(errors)
return value
In the case that all the above conditional statements are met, the output will be:
{
"detail": [
{
"loc": [
"body",
"password"
],
"msg": "Password must be at least 8 characters long. Password should contain at least one lowercase character.",
"type": "value_error"
}
]
}
Option 2
Update
In Pydantic V2, ErrorWrapper
has been removed—have a look at Migration Guide. If one would like to implement this on their own, please have a look at Pydantic V1.9 error_wrappers.py
.
Original answer
Raise ValidationError
directly, using a list of ErrorWrapper
class.
from pydantic import ValidationError
from pydantic.error_wrappers import ErrorWrapper
@validator('password', always=True)
def validate_password1(cls, value):
password = value.get_secret_value()
min_length = 8
errors = []
if len(password) < min_length:
errors.append(ErrorWrapper(ValueError('Password must be at least 8 characters long.'), loc=None))
if not any(character.islower() for character in password):
errors.append(ErrorWrapper(ValueError('Password should contain at least one lowercase character.'), loc=None))
if errors:
raise ValidationError(errors, model=User)
return value
Since FastAPI seems to be adding the loc
attribute itself, loc
would end up having the field
name (i.e., password
) twice, if it was added in the ErrorWrapper
, using the loc
attribute (which is a required parameter). Hence, you could leave it empty (using None
), which you can later remove through a validation exception handler, as shown below:
from fastapi import Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
for error in exc.errors():
error['loc'] = [x for x in error['loc'] if x] # remove null attributes
return JSONResponse(content=jsonable_encoder({"detail": exc.errors()}), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
In the case that all the above conditional statements are met, the output will be:
{
"detail": [
{
"loc": [
"body",
"password"
],
"msg": "Password must be at least 8 characters long.",
"type": "value_error"
},
{
"loc": [
"body",
"password"
],
"msg": "Password should contain at least one lowercase character.",
"type": "value_error"
}
]
}