如何验证Pydantic模型的多个字段



我想验证Pydantic模型的三个模型字段。为了做到这一点,我从pydantic导入root_validator,但我得到了以下错误:

from pydantic import BaseModel, ValidationError, root_validator
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'root_validator' from 'pydantic' (C:UsersLenovoAppDataLocalProgramsPythonPython38-32libsite-packagespydantic__init__.py)

我试过这个:

@validator
def validate_all(cls,v,values,**kwargs):

我从一些常见的字段父模型继承了我的pydantic模型。值只显示父类字段,但不显示我的子类字段。例如:

class Parent(BaseModel):
name: str
comments: str
class Customer(Parent):
address: str
phone: str

@validator
def validate_all(cls,v,values, **kwargs):
#here values showing only (name and comment) but not address and phone.

为了扩展Rahul R的答案,本示例更详细地展示了如何使用pydantic验证器。

此示例包含回答您的问题所需的所有信息。

请注意,还有一个选项可以使用@root_validator,如Kentgrav所述,有关更多详细信息,请参阅文章底部的示例。

import pydantic
class Parent(pydantic.BaseModel):
name: str
comments: str
class Customer(Parent):
address: str
phone: str
# If you want to apply the Validator to the fields "name", "comments", "address", "phone"
@pydantic.validator("name", "comments", "address", "phone")
@classmethod
def validate_all_fields_one_by_one(cls, field_value):
# Do the validation instead of printing
print(f"{cls}: Field value {field_value}")
return field_value  # this is the value written to the class field
# if you want to validate to content of "phone" using the other fields of the Parent and Child class
@pydantic.validator("phone")
@classmethod
def validate_one_field_using_the_others(cls, field_value, values, field, config):
parent_class_name = values["name"]
parent_class_address = values["address"] # works because "address" is already validated once we validate "phone"
# Do the validation instead of printing
print(f"{field_value} is the {field.name} of {parent_class_name}")
return field_value 
Customer(name="Peter", comments="Pydantic User", address="Home", phone="117")

输出

<class '__main__.Customer'>: Field value Peter
<class '__main__.Customer'>: Field value Pydantic User
<class '__main__.Customer'>: Field value Home
<class '__main__.Customer'>: Field value 117
117 is the phone number of Peter
Customer(name='Peter', comments='Pydantic User', address='Home', phone='117')

更详细地回答您的问题:

将要验证的字段添加到验证函数正上方的@validator装饰器中。

  • @validator("name")使用"name"的字段值(例如"Peter"(作为验证函数的输入。类及其父类的所有字段都可以添加到@validator装饰器中
  • 验证函数(validate_all_fields_one_by_one(然后使用字段值作为验证输入的第二自变量(field_value(。验证函数的返回值被写入类字段。验证函数的签名是def validate_something(cls, field_value),其中函数和变量名称可以任意选择(但第一个参数应该是cls(。根据Arjan的说法(https://youtu.be/Vj-iU-8_xLs?t=329),还应该添加@classmethod装饰器

如果目标是通过使用父类和子类的其他(已验证的(字段来验证一个字段,则验证函数的完整签名为def validate_something(cls, field_value, values, field, config)(参数名称valuesfieldconfig必须匹配(,其中字段的值可以使用字段名称作为键来访问(例如values["comments"](。

Edit1:如果只想检查特定类型的输入值,可以使用以下结构:

@validator("*") # validates all fields
def validate_if_float(cls, value):
if isinstance(value, float):
# do validation here
return value

Edit2:使用@root_validator:一起验证所有字段的更简单方法

import pydantic
class Parent(pydantic.BaseModel):
name: str
comments: str
class Customer(Parent):
address: str
phone: str
@pydantic.root_validator()
@classmethod
def validate_all_fields_at_the_same_time(cls, field_values):
# Do the validation instead of printing
print(f"{cls}: Field values are: {field_values}")
assert field_values["name"] != "invalid_name", f"Name `{field_values['name']}` not allowed."
return field_values

输出

Customer(name="valid_name", comments="", address="Street 7", phone="079")
<class '__main__.Customer'>: Field values are: {'name': 'valid_name', 'comments': '', 'address': 'Street 7', 'phone': '079'}
Customer(name='valid_name', comments='', address='Street 7', phone='079')
Customer(name="invalid_name", comments="", address="Street 7", phone="079")
ValidationError: 1 validation error for Customer
__root__
Name `invalid_name` not allowed. (type=assertion_error)

您需要将字段作为装饰器的参数进行传递。

class Parent(BaseModel):
name: str
comments: str
class Customer(Parent):
address: str
phone: str
@validator("name", "coments", "address", "phone")
def validate_all(cls, v, values, **kwargs):

首先,如果您在导入root_validator时出错,我会更新pydantic。

pip install -U pydantic

上面的许多示例向您展示了如何一次对多个值使用相同的验证器。或者它们增加了很多不必要的复杂性来完成你想要的。您可以使用root_validator装饰器,简单地使用以下代码在同一个验证器中同时验证多个字段:

from pydantic import root_validator
from pydantic import BaseModel
class Parent(BaseModel):
name: str = "Peter"
comments: str = "Pydantic User"
class Customer(Parent):
address: str = "Home"
phone: str = "117"
@root_validator
def validate_all(cls, values):
print(f"{values}")
values["phone"] = "111-111-1111"
values["address"] = "1111 Pydantic Lane"
print(f"{values}")
return values
Output:
{'name': 'Peter', 'comments': 'Pydantic User', 'address': 'Home', 'phone': '117'}
{'name': 'Peter', 'comments': 'Pydantic User', 'address': '1111 Pydantic Lane', 'phone': '111-111-1111'}

选项1-使用@validator装饰器

根据文献记载;单个CCD_ 23可以通过将多个字段名称"传递给它来应用于多个字段;(并且"也可以通过传递特殊值'*'"对所有字段调用"(。因此,您可以将要验证的字段添加到validator装饰器中,并且使用field.name属性可以在每次调用validator时检查要验证哪个字段。如果一个字段没有通过验证,则可以raise ValueError,";其将被捕获并用于填充CCD_ 29〃;(参见此处的"注释"部分(。如果您需要基于其他字段验证字段,则必须首先检查它们是否已经使用values.get()方法进行了验证,如本答案所示(更新2(。下面演示了一个示例,其中验证了诸如namecountry_codephone编号(基于提供的country_code(之类的字段。提供的正则表达式模式只是本演示的示例,它们基于这个和这个答案。。

from pydantic import BaseModel, validator, ValidationError
import re
name_pattern = re.compile(r'[a-zA-Zs]+$')
country_codes = {"uk", "us"}
UK_phone_pattern = re.compile(r'^(+44s?7d{3}|(?07d{3})?)s?d{3}s?d{3}$')  # UK mobile phone number. Valid example: +44 7222 555 555
US_phone_pattern = re.compile(r'^(([0-9]{3}) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$')  # US phone number. Valid example: (123) 123-1234
phone_patterns = {"uk": UK_phone_pattern, "us": US_phone_pattern}
class Parent(BaseModel):
name: str
comments: str

class Customer(Parent):
address: str
country_code: str
phone: str
@validator('name', 'country_code', 'phone')
def validate_atts(cls, v, values, field):
if field.name == "name":
if not name_pattern.match(v): raise ValueError(f'{v} is not a valid name.')
elif field.name == "country_code":
if not v.lower() in country_codes: raise ValueError(f'{v} is not a valid country code.')
elif field.name == "phone" and values.get('country_code'):
c_code = values.get('country_code').lower()
if not phone_patterns[c_code].match(v): raise ValueError(f'{v} is not a valid phone number.')
return v

选项2-使用@root_validator装饰器

另一种方法是使用@root_validator,它允许对整个模型的数据进行验证。

from pydantic import BaseModel, root_validator, ValidationError
import re
name_pattern = re.compile(r'[a-zA-Zs]+$')
country_codes = {"uk", "us"}
UK_phone_pattern = re.compile(r'^(+44s?7d{3}|(?07d{3})?)s?d{3}s?d{3}$')  # UK mobile phone number. Valid example: +44 7222 555 555
US_phone_pattern = re.compile(r'^(([0-9]{3}) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$')  # US phone number. Valid example: (123) 123-1234
phone_patterns = {"uk": UK_phone_pattern, "us": US_phone_pattern}
class Parent(BaseModel):
name: str
comments: str

class Customer(Parent):
address: str
country_code: str
phone: str
@root_validator()
def validate_atts(cls, values):
name = values.get('name')
comments = values.get('comments')
address = values.get('address')
country_code = values.get('country_code')
phone = values.get('phone')

if name is not None and not name_pattern.match(name): 
raise ValueError(f'{name} is not a valid name.')
if country_code is not None and not country_code.lower() in country_codes: 
raise ValueError(f'{country_code} is not a valid country code.')
if phone is not None and country_code is not None:
if not phone_patterns[country_code.lower()].match(phone): 
raise ValueError(f'{phone} is not a valid phone number.')

return values

此示例包含回答问题所需的所有信息。

class User(BaseModel):
name: Optional[str] = ""
class Config:
validate_assignment = True
@validator("name")
def set_name(cls, name):
return name or "foo"