본문 바로가기
FastAPI

[FastAPI] Pydantic validator의 재사용

by 혀넝 2023. 4. 21.

Validator

pydantic의 BaseModel을 사용하여 프론트에서 받을 Body 값을 지정하는 동시에 Body 값 중에 validator가 필요한 경우 pydantic model 안에서 정의할 수 있다.

아래의 예시는 pydantic 공식 페이지에서 제공하는 예시와 설명이다.

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
  • validator는 class method이기 때문에 첫 번째 인자로 UserModel의 instance가 아니라 UserModel class를 받아야 한다.
  • 두 번째 인자는 유효성 검사를 진행할 값이 들어온다.
  • 두 값은 필수이고 이후로 다른 값들도 들어올 수 있는데 아래의 키값이 변해서는 안된다. (설명은 공식 페이지에서 가져옴)
    • values: a dict containing the name-to-value mapping of any previously-validated fields
    • config: the model config
    • field: the field being validated. Type of object is pydantic.fields.ModelField
    • **kwargs: if provided, this will include the arguments above not explicitly listed in the signature
  • validator는 통과된 값을 리턴하거나 ValueError, TypeError, AssertionError를 리턴할 수 있다.

 

Validator 재사용

pydantic 공식 페이지에서 제공하는 예시가 아니라 실제 코드에서 적용시킨 예시는 아래와 같다. (내용은 조금 다름)

class UserRegisterDto(BaseModel):
    email: EmailStr
    password1: str
    password2: str
    system_language: str

    @validator("password1")
    def password_regex(cls, password):
        # 영문+숫자 8자리 이상
        regex = r"^(?=.*?[A-Za-z])(?=.*?[0-9]).{8,}$"

        if not re.match(regex, password):
            raise ValueError("Invalid password form")
        return password

주석처리로 설명한 대로 비밀번호가 영문 + 숫자 8자리 이상인지 체크하는 validator이다. 만약 틀리다면 ValuError를 리턴하기 때문에 raise ValueError를 하고 있다.

위 클래스는 회원가입 시 Body로 들어오는 값들이고 그중에서 validator로 비밀번호를 체크한다. 그러다 다른 곳에서도 같은 비밀번호 validator가 필요한 경우가 생겨 아래와 같이 validator를 함수로 분리하고, 각 클래스 안에서 재사용했다.

def password_regex(password):
    regex = r"^(?=.*?[A-Za-z])(?=.*?[0-9]).{8,}$"
    if not re.match(regex, password):
        raise ValueError("Invalid password form")
    return password

...

class UserRegisterDto(BaseModel):
    email: EmailStr
    password1: str
    password2: str
    system_language: str

    _password_regex = validator("password1", allow_reuse=True)(password_regex)


class UserPasswordChangeDto(BaseModel):
    old_password: str
    password1: str
    password2: str

    _password_regex = validator("password1", allow_reuse=True)(password_regex)
  • allow_reuse는 validator 데코레이터에서 제공하는 옵션 중 하나로, True로 설정하면 같은 validator 함수를 다른 필드에서도 사용할 수 있도록 허용한다. 즉 해당 validator 함수를 다른 필드에 대해서도 재사용할 수 있다.
  • True로 하지 않으면 같은 validator 함수를 다른 필드에 대해 사용하려고 했을 때 새로운 validator 함수를 정의해야 하는데, 이는 코드 중복을 유발하고 코드의 유지보수를 어렵게 한다. 따라서 같은 validator를 사용할 때는 위와 같이 함수로 분리해서 사용할 수 있다.
  • validator("password1", allow_reuse=True)(password_regex)는 password_regex 함수를 적용한 새로운 validator 함수를 생성하며, 이 validator 함수는 password1 필드의 값에 대해 password_regex 함수의 검증을 수행한다. 이렇게 생성된 validator 함수는 _password_regex에 할당되어, UserPasswordChangeReqDto 클래스에서 password1 필드의 검증에 사용된다.

'FastAPI' 카테고리의 다른 글

[FastAPI] 쿠키 설정 및 쿠키 삭제  (0) 2023.05.02
[FastAPI] Request에서 user 정보 얻기  (0) 2023.04.23