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"
}
]
}