即使一个失败,如何通过所有 Pydantic 验证器,然后在 FastAPI 响应中引发多个 ValueErrors?
How to go through all Pydantic validators even if one fails, and then raise multiple ValueErrors in a FastAPI response?
是否可以调用所有验证器以返回完整的错误列表?
@validator('password', always=True)
def validate_password1(cls, value):
password = value.get_secret_value()
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
return value
@validator('password', always=True)
def validate_password2(cls, value):
password = value.get_secret_value()
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
return value
当前行为似乎一次调用一个验证器。
我的 Pydantic class:
class User(BaseModel):
email: EmailStr
password: SecretStr
如果我没有在请求中包含 email
或 password
字段,那么我会在数组中得到两个验证失败,这就是我想为 password
字段,但当前行为似乎调用一个,如果失败则立即抛出错误。
你不能那样使用 Pydantic's validators;它看起来总是其中之一。
为了得到你的答案,你可以使用以下两种方法
1 - 您可以使用一个检查所有条件的主验证器
@validator('password', always=True)
def validate_password(cls, value):
password = value.get_secret_value()
validate_password1(password)
validate_password2(password)
return value
def validate_password1(password):
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
def validate_password2(password):
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
2 - 您可以在模型中使用重复变量来检查条件
class User(BaseModel):
email: EmailStr
password: SecretStr
password2: SecretStr
显然,您的装饰器应该是:
@validator('password', always=True)
def validate_password1(cls, value):
和
@validator('password2', always=True)
def validate_password2(cls, value):
更新: OP 想提出所有错误,所以更新后的答案如下。
除了第一个项目符号,您还可以尝试类似的方法:
@validator('password', always=True)
def validate_password(cls, value):
password = value.get_secret_value()
try:
validate_password1(password)
except Exception as e:
print('First error: ' + str(e))
try:
validate_password2(password)
except Exception as e:
print('Second error: ' + str(e))
return value
def validate_password1(password):
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
def validate_password2(password):
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
但是,返回 value
时要小心。您可以尝试在主代码中再添加一个自定义异常。
您不能以您在问题中展示的方式为特定领域提出多个 Validation errors/exceptions。下面给出了建议的解决方案。
选项 1
使用单个变量连接错误消息,并在末尾引发一次 ValueError
(如果发生错误):
@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
在满足上述所有条件语句的情况下,输出将是:
{
"detail": [
{
"loc": [
"body",
"password"
],
"msg": "Password must be at least 8 characters long. Password should contain at least one lowercase character.",
"type": "value_error"
}
]
}
选项 2
提高 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
由于 FastAPI 似乎正在添加 loc
属性本身,因此 loc
最终将具有 field
名称(即 password
)两次,如果它是使用 loc
属性(这是必需参数)添加到 ErrorWrapper
中。因此,您可以将其留空(使用 None
),稍后您可以通过 validation exception handler 将其删除,如下所示:
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(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=jsonable_encoder({"detail": exc.errors()}))
在满足上述所有条件语句的情况下,输出将是:
{
"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"
}
]
}
是否可以调用所有验证器以返回完整的错误列表?
@validator('password', always=True)
def validate_password1(cls, value):
password = value.get_secret_value()
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
return value
@validator('password', always=True)
def validate_password2(cls, value):
password = value.get_secret_value()
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
return value
当前行为似乎一次调用一个验证器。
我的 Pydantic class:
class User(BaseModel):
email: EmailStr
password: SecretStr
如果我没有在请求中包含 email
或 password
字段,那么我会在数组中得到两个验证失败,这就是我想为 password
字段,但当前行为似乎调用一个,如果失败则立即抛出错误。
你不能那样使用 Pydantic's validators;它看起来总是其中之一。
为了得到你的答案,你可以使用以下两种方法
1 - 您可以使用一个检查所有条件的主验证器
@validator('password', always=True)
def validate_password(cls, value):
password = value.get_secret_value()
validate_password1(password)
validate_password2(password)
return value
def validate_password1(password):
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
def validate_password2(password):
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
2 - 您可以在模型中使用重复变量来检查条件
class User(BaseModel):
email: EmailStr
password: SecretStr
password2: SecretStr
显然,您的装饰器应该是:
@validator('password', always=True)
def validate_password1(cls, value):
和
@validator('password2', always=True)
def validate_password2(cls, value):
更新: OP 想提出所有错误,所以更新后的答案如下。
除了第一个项目符号,您还可以尝试类似的方法:
@validator('password', always=True)
def validate_password(cls, value):
password = value.get_secret_value()
try:
validate_password1(password)
except Exception as e:
print('First error: ' + str(e))
try:
validate_password2(password)
except Exception as e:
print('Second error: ' + str(e))
return value
def validate_password1(password):
min_length = 8
if len(password) < min_length:
raise ValueError('Password must be at least 8 characters long.')
def validate_password2(password):
if not any(character.islower() for character in password):
raise ValueError('Password should contain at least one lowercase character.')
但是,返回 value
时要小心。您可以尝试在主代码中再添加一个自定义异常。
您不能以您在问题中展示的方式为特定领域提出多个 Validation errors/exceptions。下面给出了建议的解决方案。
选项 1
使用单个变量连接错误消息,并在末尾引发一次 ValueError
(如果发生错误):
@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
在满足上述所有条件语句的情况下,输出将是:
{
"detail": [
{
"loc": [
"body",
"password"
],
"msg": "Password must be at least 8 characters long. Password should contain at least one lowercase character.",
"type": "value_error"
}
]
}
选项 2
提高 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
由于 FastAPI 似乎正在添加 loc
属性本身,因此 loc
最终将具有 field
名称(即 password
)两次,如果它是使用 loc
属性(这是必需参数)添加到 ErrorWrapper
中。因此,您可以将其留空(使用 None
),稍后您可以通过 validation exception handler 将其删除,如下所示:
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(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=jsonable_encoder({"detail": exc.errors()}))
在满足上述所有条件语句的情况下,输出将是:
{
"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"
}
]
}