Python (with. Code)/Django-ninja

[Django Ninja] Validation 구현 방법 (with. Pydantic)

yubi5050 2023. 10. 4. 10:23

서비스를 개발하면서 들어오는 파라미터에 대해 검증하는 로직을 작성하였고,

해당 필드만 검증하거나, 여러 필드를 한번에 검증해야 된다거나 등등의 여러 경우가 있었다.

사용한 여러 방법들에 대해 정리해보려고 한다.

 

기본적으로 Django Ninja의 Schema는 Pydantic의 BaseModel을 wrapping 하여 만들어 졌기에,

validation은 BaseModel의 @validator, @root_validator 등의 기호를 사용하여 진행하였다. (pydantic 1.x)

 

1.  타입 검증 (Type Check Validation)

기본적으로 Type에 대한 체크는 Ninja의 Schema에서 선언시 내부적으로 타입을 체크 해준다.

만약 유효하지 않은 Type을 보낼시, 422 엔티티 에러가 발생한다. 

 

추가로 Pydantic은 parsing 라이브러리 이기 때문에, str이라고 선언하고 int를 입력시에도 자동으로 문자열로 읽어 준다.

따라서 좀더 강한 Typing을 위해선 StrictInt, StrictStr과 같은 모듈을 사용하는 것도 좋다.

from ninja import Schema
from pydantic import StrictStr

# Schema는 Pydantic의 BaseModel을 상속 받아 만들어짐
class TestSchemaIn(Schema):
    name1: str
    name2: StrictStr

 

Ninja의 Schema에서 Validation 과정과 ValidationError 부분의 코드를 보고 싶다면, ninja/operation.py 의 run() , get_values() 함수에 내용이 나와있으며, Pydantic의 validation 규칙에 어긋나면 최종 ninja의 ValidationError를 호출한다.

 

 

2. 값 검증 (Value Validation)

값에 대한 Validation은 방법이 두 가지 있으며, 모두 Pydantic에서 제공하는 모듈을 이용할 수 있다.

1. Field 모듈을 이용해서 Validation 처리를 하는 방법

2. @validator 데코레이터를 이용해서 해당 필드에 대해서 직접 검증을 진행하고 처리 하는 방법

 

1. Field 모듈을 이용하여 Validation 처리

- Pydantic에서 제공하는 Field모듈에는 gt(greater than), ge(greater than or equal to), lt(less than), le(less than or equal to), allow_inf_nan, max_digits, unique_items, min_length, max_length, discriminator 등 과 같은 다양한 파라미터가 있다. 

- 해당 파라미터를 통해 Validation 처리를 할 수 있다.

from ninja import Schema
from pydantic import StrictStr

# Field 를 통해 문자열에 대한 min, max 길이 설정 가능
class TestSchemaIn(Schema):
    name: str = Field(min_length=10, max_length=30)

 

2. Validator 데코레이터를 이용한 Validation 처리

- Pydantic에서 제공하는 Validator 데코레이터를 이용해 특정 필드에 대해 Validation을 처리 할 수 있다.

- @validator 데코레이터는 해당 필드 값을 인자로 받아 유효성 검사를 수행하고, 필요한 경우 값을 변환하거나 수정도 가능하다.

from ninja import Schema
from ninja.errors import ValidationError
from pydantic import StrictStr

class TestSchemaIn(Schema):
    name: StrictStr

    # Validator 데코레이터를 를 통해 문자열에 대한 길이 설정 가능    
    @validator("name")
    def name_validation(cls, v):
        if 10 < len(v) < 30:  # name 길이 10~30
            raise ValidationError()
    	return v

 

3. Validator 데코레이터를 이용한 Validation 여러 필드 처리 (1) - 여러개 선언

- 만약 필드 검증간 여러 필드에 대해서 검증할 필요가 있는 경우 다음과 같이 여러개를 선언하여 검증도 가능하다. 

from pydantic import validator, StrictStr
from ninja.errors import ValidationError
from ninja import Schema

class TestSchemaIn(Schema):
    name1: StrictStr
    name2: StrictStr

    # values에는 name1만 들어있음
    @validator("name1", "name2")
    def name_validation(cls, v1, v2):
        name1 = v1
        name2 = v2
        if len(name1) <= 0:
            raise ValidationError
            
        if len(name2) <= 0:
            raise ValidationError
        return name1, name2

 

4. Validator 데코레이터를 이용한 Validation 여러 필드 처리 (2) - values

- 만약 필드 검증간 여러 필드에 대해서 검증할 필요가 있는 경우 다음과 같이 두번째 인자(values)를 통해 묶어서 받을 수도 있다.

- 참고로 해당 validator 데코레이터는 해당 필드보다 위에 선언된 필드만을 참조가 가능하기에 인자(values)에는 name1 만 들어있다.

from pydantic import validator, StrictStr
from ninja.errors import ValidationError
from ninja import Schema

class TestSchemaIn(Schema):
    name1: StrictStr
    name2: StrictStr
    name3: StrictStr

    # values에는 name1만 들어있음
    @validator("name2")
    def name1_is_not_null(cls, v, values):
        name1 = values['name1']
        name2 = v
        if len(name2) > name1:
            raise ValidationError

 

5. Validator 데코레이터를 이용한 Validation 여러 필드 처리 (3) - root_validator

- 필드 검증간 여러 필드에 대해서 가장 일반적인 방법이다.

- 필드 순서에 대한 종속성이 상관없고 맨 마지막에 실행된다. (물론 pre=True 옵션을 통해 root_validator 먼저 실행도 가능하다)

from pydantic import validator, StrictStr
from ninja.errors import ValidationError
from ninja import Schema

class TestSchemaIn(Schema):
    name1: StrictStr
    name2: StrictStr
    name3: StrictStr

    # values에는 모든 필드 값이 들어있음
    @root_validator
    def name_all_validation(cls, values):
        name1 = values['name1']
        name2 = values['name2']
        name3 = values['name3']
        if len(name1 + name2 + name3) > 20 :
            raise ValidationError