pydantic learning and use - 4 Use of validator validators (pre and each_item validators)

preface

validator uses decorators to implement custom validation and complex relationships between objects.

Validator

1. Verify that the name field contains spaces
2. Verify that username must be composed of letters and numbers
3. Verify that password 1 and password 2 are equal

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

Some precautions about verifier:

  • Validators are "class methods", so the first parameter value they receive is the UserModel class (cls), not the instance of UserModel (self)
  • The second parameter is always the field value to be verified; You can name it as you like
  • You can also add any subset of the following parameters to the signature (names must match):
    values: a dictionary that contains the name to value mapping of any previously validated field
    config: model configuration
    Field: the field being verified. The type of object is pydantic fields. ModelField.
    **kwargs: if provided, this will include the above parameters not explicitly listed in the signature
  • The verifier should return the parsed value or raise a valueerror, typeerror, or assertion error (assert can use statements).
  • When the verifier depends on other values, you should note:
    Validation is done in the defined order fields. For example, in the above example, password2 can access password1(and name), but password1 cannot access password2 For more information about how fields are sorted, see field sorting
    If the validation of another field fails (or the field is missing), it will not be included in values, so if 'password1' in values and... In this example.
    Running example
user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)  
print(user.dict())

Operation results:
name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
{'name': 'Samuel Colvin', 'username': 'scolvin', 'password1': 'zxcvbn', 'password2': 'zxcvbn'}

pre and each_item verifier

Validators can do more complex things:

  • You can apply a single validator to multiple fields by passing multiple field names
  • You can also call a single verifier '*' on all fields by passing a special value
  • The keyword parameter pre will cause the verifier to be called before other verifications
  • Pass each_item=True causes the validator to be applied to a single value (for example, of List, Dict, Set, etc.) instead of the entire object

pre=True keyword parameter pre will cause the verifier to be called before other verifications

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


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

    # '*' here is to match any field, including friends and books
    @validator('*', pre=True)
    def split_str(cls, v):
        """If the passed parameter is a string, cut it according to the comma list"""
        if isinstance(v, str):
            return v.split(',')
        return v

    @validator('books')
    def books_greater_then_5(cls, v):
        """judge books Quantity less than 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]}

Although books is defined to transmit list of int, a preprocessing is added during verification. When it is judged to be a string, it will be converted to list.

each_item=True causes the validator to be applied to a single value (for example, of List, Dict, Set, etc.) instead of the entire object

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


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

    # '*' here is to match any field, including friends and books
    @validator('*', pre=True)
    def split_str(cls, v):
        """If the passed parameter is a string, cut it according to the comma list"""
        if isinstance(v, str):
            return v.split(',')
        return v

    @validator('books')
    def books_greater_then_5(cls, v):
        """judge books Quantity less than 5"""
        if len(v) > 5:
            raise ValueError('books greater than 5')
        return v

    @validator('friends', each_item=True)
    def check_friends(cls, v):
        """inspect friends The number of single value inside is greater than 1"""
        assert v >= 1, f'{v} is not greater then 1'
        return v

    @validator('books', each_item=True)
    def check_books(cls, v):
        """books The single value inside is greater than 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]}

The validator can also pass multiple field names*

# '*' here is to match any field, including friends and books
    @validator('*', pre=True)
    def split_str(cls, v):
        """If the passed parameter is a string, cut it according to the comma list"""
        if isinstance(v, str):
            return v.split(',')
        return v

Equivalent to

@validator('friends', 'books', pre=True)
    def split_str(cls, v):
        """If the passed parameter is a string, cut it according to the comma list"""
        if isinstance(v, str):
            return v.split(',')
        return v

Subclass validator and each_item

If you use a validator with a subclass that references a type field on the List parent class, use each_item=True will cause the verifier not to run; Instead, the List must be iterated programmatically.

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 verify always=True

For performance reasons, by default, validators are not invoked for fields when no value is provided. However, in some cases, it may be useful or necessary to always call the validator, such as setting dynamic defaults.

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)

You often want to use it with pre, otherwise always=True pydantic will try to verify the default value of None, which will cause errors.

Keywords: Python

Added by leetee on Fri, 25 Feb 2022 02:48:37 +0200