[Fast API] Endpoint Validation (422 Error: Unprocessable Entity)
Fast API 애플리케이션 개발 시에 422 Error가 발생할 경우가 있다.
보통 API 요청 시 pydantic으로 정의되어 있는 Request Model에 어긋나기 때문에 발생하는데 그 외에도 endpoint, router 선언 기준/선언 순서에 따라 endpoint가 겹치는 현상이 발생하여 오류 추적이 어려울 때가 존재하여 그 내용을 정리하였다.
Endpoint 정의 및 순서에 따른 이슈
: 서로 다른 두 개의 API를 선언하고 테스트 결과를 확인해 보았다.
from fastapi import Body, FastAPI, Path
from model import UserInfo, UserStatus
app = FastAPI()
# 사용자 정보 수정 API
@app.put("/user/{id}")
def update_user(id: int = Path(...),request: UserInfo = Body(...),):
user = update_user_info(id, request)
return user
# 사용자 상태 수정 API
@app.put("/user/status")
def update_status(request: UserStatus):
user = update_user_status(request)
return user
def update_user_info(id, request):
# DB Connection하여 user정보를 수정하는 함수로 가정함.
return id
def update_user_status(request):
# DB Connection하여 user의 status를 수정하는 함수로 가정함.
return request
- 비교적 간단한 API 두 개를 선언하였으며, update_user_info, status 등 사용자 정보를 가져오거나 수정하는 부분은 구현하지 않았으나 구현되어 있는 것으로 가정하고 진행하였다.
- 사용자 정보 수정 API는 정상적으로 동작을 하였다. 문제가 된 부분은 사용자 상태 수정 API인데, 그 원인은 무엇일까?
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
class UserInfo(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserStatus(BaseModel):
user_id: int
status: str
class Board(BaseModel):
board_id: int
title: str
content: str
- API의 model은 다음과 같이 선언되어 있으며, 아래의 swagger를 보면 알겠지만 model에 맞춰 정상적으로 호출한 것으로 보인다.
- 실제 호출한 Curl을 봐도 정상적인 형태로 API를 호출한 것으로 확인이 된다.
왜 실패했는가?
: 원인은 사용자 정보 수정 API에 숨어있다.
- 두 API의 사양을 비교해 보자
1) /user/{id}
2) /user/status
문제가 없는 것처럼 보일 수 있지만 두 API는 호출하는 endpoint가 겹치는 이슈가 존재한다.
두 API는 HTTP Method가 동일하며 endpoint 또한 "/user/status"를 호출한다면 1번 함수를 호출하고 {id} 파라미터에 status를 입력한 것으로 인지하게 된 것이다.
어떻게 해결해야 하는가?
: API의 호출순서를 변경하자
from fastapi import Body, FastAPI, Path
from model import UserInfo, UserStatus
app = FastAPI()
# 사용자 상태 수정 API
@app.put("/user/status")
def update_status(request: UserStatus):
user = update_user_status(request)
return user
# 사용자 정보 수정 API
@app.put("/user/{id}")
def update_user(id: int = Path(...),request: UserInfo = Body(...),):
user = update_user_info(id, request)
return user
def update_user_info(id, request):
# DB Connection하여 user정보를 수정하는 함수로 가정함.
return id
def update_user_status(request):
# DB Connection하여 user의 status를 수정하는 함수로 가정함.
return request
- API의 호출순서를 변경하고 테스트해 보자
- 호출이 정상적으로 성공한 것으로 확인된다.
이 외에도 router로 선언하는 방법이 존재한다. router로 endpoint를 나눠서 선언하고 main.py에서 router를 호출하는 순서로 조정이 가능하다.
하지만, 이를 방지하기 위해서는 초기 API 설계 시 endpoint를 확실하게 나누고 겹치지 않게 하는 것이 더 중요할 것으로 생각된다.
위와 같은 이슈가 발생한다면 애초에 API를 다른 것을 호출하기 때문에 디버깅을 하기 어렵고 추측을 하여 찾아내야 되기 때문에 비교적 시간이 오래 걸릴 수 있다.
결론: API 설계 단계에 또는 신규 기능추가 시 API들끼리의 충돌이 발생하지 않게 주의가 필요하다.