고민은 격렬하게, 행동은 단순하게

[FastAPI] pydantic @root_validator 통해 유효성 검증 본문

개발

[FastAPI] pydantic @root_validator 통해 유효성 검증

jomminii 2023. 4. 12. 09:01

API 를 개발하게 되면 Request 가 잘 들어왔는지 검증하는 로직을 추가하게 됩니다.

request 모델로 받겠다고 정의한 schema 에서 service
로직이 아닌 request 자체에 대한 유효성 검증을 대부분 처리하고 있는데, 이때 @root_validator 를 사용하면 보다 효율적으로 처리가 가능합니다.


아래와 같이 요청을 받는 router 가 존재하고,

# router.py

@router.put(
    "/entity/capacity",
    response_model=ApiRes,
    summary="Update Capacity of an Abstract Entity",
)
async def update_entity_capacity(
    req: EntityCapacityUpdateReq,
    ...
) -> ApiRes:
    """ Update the capacity of an abstract entity
    Update the maximum capacity for a specified abstract entity.

    # Replace this with your service logic for updating the entity capacity
    await entity_service.update_entity_capacity(
        req=req,
        ...
    )
    return ApiRes.of_success()




router 에서 받는 request 모델을 정의한 schema

# schema.py

from pydantic import BaseModel, Body, root_validator

class CapacityUpdateRequest(BaseModel):
    entity_id: int = Body(None, alias="entityId", description="Entity ID")
    max_capacity: int = Body(..., alias="maxCapacity", description="Maximum capacity")

    @root_validator(pre=True)
    def values_validation(cls, values):
        entity_id = values.get("entity_id")
        max_capacity = values.get("max_capacity")

        if entity_id is None:
            raise ValueError("Invalid access. Please provide an entity ID.")
        if max_capacity is None:
            raise ValueError(f"Maximum capacity is required. maxCapacity: {max_capacity}")
        if max_capacity < 0 or max_capacity > 10000:
            raise ValueError(f"maxCapacity must be between 0 and 10,000. maxCapacity: {max_capacity}")
        return values

간단하게만 로직을 처리하자면 위의 entity_id: int = Body(None, description="Entity ID") 와 같은 식으로 타입을 정의하고 Body를 통해 default value 나 설명, 또는 간단한 유효성 체크 로직을 작성할 수 있습니다.


하지만 위처럼 작성하게 되면 원하지 않는 request value 가 들어왔을 때 exception 메시지가 pydantic 에서 정의한대로 나가서 원하는대로 커스텀하기 어렵습니다. 유효성을 보다 더 자세하게 체크하고 싶은 것도 쉽지 않고요.

이때 pydantic 에서 제공하는 @root_validator 를 사용할 수 있습니다.


root_validator 는 schema 내에 추가하여 request 로 넘어온 값들을 추가로 검증할 수 있게 해줍니다.

pre= argument 를 통해 적용 순서를 정할 수 있는데 True 로 하게 되면 entity_id:int 로직을 먼저 타기 전에 @root_validator 를 타게 됩니다.

이때의 이점은 타입체크를 명확히 할 수 있다는 것인데, pre=False로 할 경우 int 타입에 str이 들어오면 에러를 내는게 아니라 값을 None 으로 치환해서 @root_validator로 넘겨줍니다. 이러면 실제 이 값이 타입이 잘못된건지 아예 안들어온건지 명확하지가 않아 관련 exception 을 내보내기 어려워집니다.

pre=True 로 하게 되면 값은 그대로 넘어오고 @root_validator 로직을 탄 후에 entity_id:int 로직으로 넘어가서 타입이 다를 경우 타입 exception 을 내보내줍니다.


요런식으로요

"1 validation error for Request\nbody -> __root__\n  '<' not supported between instances of 'str' and 'int' (type=type_error)",

하나 참고할 건 pre=True로 할 경우 @root_validator를 먼저 타서 values 에 entity_id가 아닌 entityId로 들어옵니다.


위 내용들 참고해서 유효성 검증 로직 잘 짜봅시다.

반응형