반응형

Pydantic Validators (유효성 검증 기능)

사용자 유효성 검증이 필요하면 validator 데코레이터와 클래스 메서드를 이용한다.

다음 예제는 모델을 만들고 모델 필드 값에 필요한 유효성 검증 메서드를 구현하고 있다.

 

from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel): # 모델 
    name: str
    username: str
    password1: str
    password2: str
    @validator('name') # name 값에 대한 유효성 검사하는 클래스 메서드
    def name_must_contain_space(cls, v): # 이름에 빈칸이 있는지 검사 (이름 성 구분)
        if ' ' not in v:
            raise ValueError('이름과 성 사이에는 빈 칸이 필요합니다.')
        return v.title()
    @validator('password2') # password2 값에 대한 유효성 검사하는 클래스 메서드
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']: # 두 암호 입력 값이 같은가 검사
            raise ValueError('입력한 비밀번호가 서로 다릅니다.')
        return v
    @validator('username') # username(ID) 값에 대한 유효성 검사하는 클래스 메서드
    def username_alphanumeric(cls, v):	
        assert v.isalnum(), 'must be alphanumeric' # 알파벳과 숫자로 이루어졌는지 검사
        return v
user = UserModel(
    name='Sangmun Oh',
    username='sualchi',
    password1='pass1234',
    password2='pass1234',
)
print(user)
try:
    UserModel(
        name='Sangmun',
        username='sualchi',
        password1='pass1234',
        password2='pass12345',
    )
except ValidationError as e:
    print(e)
[실행 결과]
    name='Sangmun Oh' username='sualchi' password1='pass1234' password2='pass1234'
    2 validation errors for UserModel
    name
      이름과 성 사이에는 빈 칸이 필요합니다. (type=value_error)
    password2
      입력한 비밀번호가 서로 다릅니다. (type=value_error)

[참고] 유효성 검사 메서드는 ValueError, TypeError,  AssertionError 발생할 수도 있다. Assert 이용한 에러 발생은 실행 옵션에 따라서 동작하지 않을 있다.

사전 또는 항목별 유효성 검사?

Validator 예제보다 복잡한 것도 수행할 있다.

from typing import List
from pydantic import BaseModel, ValidationError, validator
class DemoModel(BaseModel):
    square_numbers: List[int] = []
    cube_numbers: List[int] = []
    # 이 예제에서 '*'은 'cube_numbers', 'square_numbers' 지정과 같다:
    @validator('*', pre=True) # ‘*’은 모든 필드를 의미한다.
    def split_str(cls, v):
        if isinstance(v, str):
            return v.split('|')
        return v
    @validator('cube_numbers', 'square_numbers') 
    def check_sum(cls, v):
        if sum(v) > 42:
            raise ValueError('합은 42보다 커야 한다.')
        return v
    @validator('square_numbers', each_item=True)
    def check_squares(cls, v):
        assert v ** 0.5 % 1 == 0, f'{v}은(는) 제곱수가 아니다'
        return v
    @validator('cube_numbers', each_item=True)
    def check_cubes(cls, v):
        # 64 ** (1 / 3) == 3.9999999999999996 (!)
        # this is not a good way of checking cubes
        assert v ** (1 / 3) % 1 == 0, f'{v}은(는) 세제곱 수가 아니다'
        return v
print(DemoModel(square_numbers=[1, 4, 9]))
#> square_numbers=[1, 4, 9] cube_numbers=[]
print(DemoModel(square_numbers='1|4|16'))
#> square_numbers=[1, 4, 16] cube_numbers=[]
print(DemoModel(square_numbers=[16], cube_numbers=[8, 27]))
#> square_numbers=[16] cube_numbers=[8, 27]
try:
    DemoModel(square_numbers=[1, 4, 2])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    square_numbers -> 2
      2은(는) 제곱수가 아니다 (type=assertion_error)
    """
try:
    DemoModel(cube_numbers=[27, 27])
except ValidationError as e:
    print(e)
    """
    1 validation error for DemoModel
    cube_numbers
      합은 42보다 커야 한다. (type=value_error)
    """

[참고] each_item=True 개별 값에 대한 유효성 검사를 진행한다(; List, Dict, Set )

하위 클래스 유효성 검사  each_item?

리스트 값의 경우에는 하위 값에 대한 유효성 검사가 필요할 있다. 이런 경우에는 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 != '', '빈 문자열을 허용하지 않습니다.'
        return v
# 이 경우에는 하위 항목에 대한 유효성 검사를 하지 않는다.
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 != '', '빈 문자열을 허용하지 않습니다.'
        return v
try:
    child = ChildModel2(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
    print(e)
    """
    1 validation error for ChildModel2
    names
      빈 문자열을 허용하지 않습니다. (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, 5, 11, 20, 28, 48, 380781)
print(DemoModel(ts='2017-11-08T14:00'))
#> ts=datetime.datetime(2017, 11, 8, 14, 0)

 

반응형

+ Recent posts