pydantic學習與使用-4.validator 驗證器的使用(pre 和 each_itemm 驗證器)


前言

validator 使用裝飾器可以實現自定義驗證和對象之間的復雜關系。

驗證器

1.校驗name字段包含空格
2.校驗username 必須是字母和數字組成
3.校驗密碼1和密碼2相等

from pydantic import BaseModel, ValidationError, validator


class UserModel(BaseModel):
    name: str
    username: str
    password1: str
    password2: str

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v

    @validator('username')
    def username_alphanumeric(cls, v):
        assert v.isalnum(), 'must be alphanumeric'
        return v

關於驗證器的一些注意事項:

  • 驗證器是“類方法”,因此它們接收的第一個參數值是UserModel類(cls),而不是UserModel的實例(self)
  • 第二個參數始終是要驗證的字段值;可以隨意命名
  • 您還可以將以下參數的任何子集添加到簽名中(名稱必須匹配):
    values:包含任何先前驗證字段的名稱到值映射的字典
    config:模型配置
    field:正在驗證的字段。對象的類型是pydantic.fields.ModelField。
    **kwargs:如果提供,這將包括上述未在簽名中明確列出的參數
  • 驗證器應該返回解析后的值或引發 a ValueError, TypeError, or AssertionError (assert可以使用語句)。
  • 在驗證器依賴其他值的情況下,您應該注意:
    驗證是在定義的訂單字段中完成的。例如,在上面的示例中,password2可以訪問password1(and name),但password1不能訪問password2. 有關字段如何排序 的更多信息,請參閱字段排序
    如果另一個字段的驗證失敗(或該字段丟失),它將不會包含在 中values,因此 if 'password1' in values and ...在此示例中。
    運行示例
user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)  
print(user.dict())

運行結果:
name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
{'name': 'Samuel Colvin', 'username': 'scolvin', 'password1': 'zxcvbn', 'password2': 'zxcvbn'}

pre 和 each_item 驗證器

驗證器可以做一些更復雜的事情:

  • 通過傳遞多個字段名稱,可以將單個驗證器應用於多個字段
  • 也可以通過傳遞特殊值在所有字段上調用單個驗證器'*'
  • 關鍵字參數pre將導致驗證器在其他驗證之前被調用
  • 傳遞each_item=True將導致驗證器應用於單個值(例如 of List、Dict、Set等),而不是整個對象

pre=True 關鍵字參數pre將導致驗證器在其他驗證之前被調用

from pydantic import BaseModel, ValidationError, validator
from typing import List


class DemoModel(BaseModel):
    friends: List[int] = []
    books: List[int] = []

    # '*' 在這里是匹配任意字段,包含friends,books
    @validator('*', pre=True)
    def split_str(cls, v):
        """如果傳參是字符串,根據逗號切割成list"""
        if isinstance(v, str):
            return v.split(',')
        return v

    @validator('books')
    def books_greater_then_5(cls, v):
        """判斷books數量少於5"""
        if len(v) > 5:
            raise ValueError('books greater than 5')
        return v


a1 = {
    "friends": [2, 3, 4],
    "books": "3,4,5"
}
d = DemoModel(**a1)
print(d)  # friends=[2, 3, 4] books=[3, 4, 5]
print(d.dict())  # {'friends': [2, 3, 4], 'books': [3, 4, 5]}

雖然定義了books傳list of int ,但是在校驗的時候,加了個預處理,判斷是字符串的時候,會轉成list。

each_item=True 將導致驗證器應用於單個值(例如 of List、Dict、Set等),而不是整個對象

from pydantic import BaseModel, ValidationError, validator
from typing import List


class DemoModel(BaseModel):
    friends: List[int] = []
    books: List[int] = []

    # '*' 在這里是匹配任意字段,包含friends,books
    @validator('*', pre=True)
    def split_str(cls, v):
        """如果傳參是字符串,根據逗號切割成list"""
        if isinstance(v, str):
            return v.split(',')
        return v

    @validator('books')
    def books_greater_then_5(cls, v):
        """判斷books數量少於5"""
        if len(v) > 5:
            raise ValueError('books greater than 5')
        return v

    @validator('friends', each_item=True)
    def check_friends(cls, v):
        """檢查friends 里面單個值數字大於1"""
        assert v >= 1, f'{v} is not greater then 1'
        return v

    @validator('books', each_item=True)
    def check_books(cls, v):
        """books 里面單個值大於2"""
        assert v >= 2, f'{v} is not greater then 2'
        return v


a1 = {
    "friends": [2, 3, 4],
    "books": "3,4,5"
}
d = DemoModel(**a1)
print(d)  # friends=[2, 3, 4] books=[3, 4, 5]
print(d.dict())  # {'friends': [2, 3, 4], 'books': [3, 4, 5]}

validator傳遞多個字段名稱,也可以傳*

# '*' 在這里是匹配任意字段,包含friends,books
    @validator('*', pre=True)
    def split_str(cls, v):
        """如果傳參是字符串,根據逗號切割成list"""
        if isinstance(v, str):
            return v.split(',')
        return v

等價於

@validator('friends', 'books', pre=True)
    def split_str(cls, v):
        """如果傳參是字符串,根據逗號切割成list"""
        if isinstance(v, str):
            return v.split(',')
        return v

子類驗證器和each_item

如果使用帶有引用List父類上的類型字段的子類的驗證器,使用each_item=True將導致驗證器不運行;相反,必須以編程方式迭代列表。

from typing import List
from pydantic import BaseModel, ValidationError, validator


class ParentModel(BaseModel):
    names: List[str]


class ChildModel(ParentModel):
    @validator('names', each_item=True)
    def check_names_not_empty(cls, v):
        assert v != '', 'Empty strings are not allowed.'
        return v


# This will NOT raise a ValidationError because the validator was not called
try:
    child = ChildModel(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
    print(e)
else:
    print('No ValidationError caught.')
    #> No ValidationError caught.


class ChildModel2(ParentModel):
    @validator('names')
    def check_names_not_empty(cls, v):
        for name in v:
            assert name != '', 'Empty strings are not allowed.'
        return v


try:
    child = ChildModel2(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
    print(e)
    """
    1 validation error for ChildModel2
    names
      Empty strings are not allowed. (type=assertion_error)
    """

始終驗證always=True

出於性能原因,默認情況下,當未提供值時,不會為字段調用驗證器。但是,在某些情況下,始終調用驗證器可能很有用或需要,例如設置動態默認值。

from datetime import datetime

from pydantic import BaseModel, validator


class DemoModel(BaseModel):
    ts: datetime = None

    @validator('ts', pre=True, always=True)
    def set_ts_now(cls, v):
        return v or datetime.now()


print(DemoModel())
#> ts=datetime.datetime(2021, 12, 31, 15, 4, 57, 629206)
print(DemoModel(ts='2017-11-08T14:00'))
#> ts=datetime.datetime(2017, 11, 8, 14, 0)

您經常希望將它與 一起使用pre,否則always=True pydantic會嘗試驗證None會導致錯誤的默認值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM