FastAPI에서 발생하는 예외를 재정의하고 커스터마이징하는 것은 애플리케이션에서 발생하는 다양한 오류에 대해 더 유연하고 명확한 응답을 제공하는 데 유용하다. 기본적인 예외를 커스터마이즈하면 사용자에게 적절한 정보를 제공하고, 오류 상황을 관리하기 쉽게 만들 수 있다.
다음은 FastAPI에서 다양한 종류의 Exception
을 재정의하고 처리하는 방법을 알아보자.
FastAPI에서 커스텀 예외 처리하기
FastAPI는 기본적으로 다양한 HTTP 오류 상태 코드에 대해 예외 처리를 자동으로 제공한다. 그러나 프로젝트에서 보다 구체적인 오류 처리와 맞춤형 응답을 원할 경우, 기본 예외 처리를 재정의하거나 새로운 예외 처리를 추가할 수 있다.
1. FastAPI의 기본 예외 처리
FastAPI는 HTTPException
클래스를 사용하여 기본적인 예외 처리를 제공한다. 예를 들어, 404 Not Found 오류나 400 Bad Request 오류는 FastAPI에서 자동으로 처리된다. 예를 들어, 다음과 같은 코드에서 HTTPException
을 사용할 수 있다.
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/users")
async def find(
user_id: str,
):
if user_id == "not_found":
raise HTTPException(status_code=404, detail="User not found")
return user_id
응답
HTTP/1.1 404 Not Found
{
"detail": "User not found"
}
위 코드에서는 FastAPI가 404 에러를 처리하며, 사용자에게 "User not found"라는 메시지를 반환한다. 하지만 때로는 더 복잡한 예외 처리 로직이 필요할 수 있다.
2. 커스텀 예외 만들기
사용자 정의 예외를 만들기 위해 Python의 표준 예외 클래스에서 상속받아 커스텀 예외를 정의할 수 있. 예를 들어, 데이터베이스 오류나 인증 오류와 같은 특정 상황에 맞는 예외를 정의할 수 있다.
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
이 예외를 처리하는 FastAPI 핸들러를 다음과 같이 정의할 수 있다.
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=400,
content={"message": f"An error occurred: {exc.name}"},
)
@app.get("/custom_exception/{name}")
async def get_custom_exception(name: str):
if name == "error":
raise CustomException(name=name)
return {"message": f"Hello, {name}"}
위 코드는 CustomException
이 발생하면 400 상태 코드와 함께 사용자 정의 오류 메시지를 반환한다. /custom_exception/error
경로로 접근 시 커스텀 예외가 발생하고, 그 외의 경우 정상적인 응답을 받게 된다.
3. FastAPI의 기본 예외 재정의하기
기본 예외인 HTTPException
도 재정의하여 응답 메시지나 상태 코드를 변경할 수 있습니다. 이를 위해 기본 예외를 처리하는 핸들러를 정의한다.
from fastapi.exceptions import HTTPException
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"detail": f"Oops! {exc.detail}"}
)
이 코드는 HTTPException
이 발생할 때마다 "Oops!"로 시작하는 오류 메시지를 반환한다.
응답
{
"detail": "Oops! User not found"
}
재정하기 전에는 "User not found"라고 표시되었지만 재정의하여 다르게 출력할 수 있다.
4. 여러 예외 처리하기
프로젝트에 다양한 예외가 존재할 경우, 여러 개의 예외 핸들러를 정의할 수 있다. 이를 통해 각 예외 상황에 맞는 처리 로직을 제공할 수 있다.
class NotFoundException(Exception):
def __init__(self, user_id: str):
self.user_id = user_id
@app.exception_handler(NotFoundException)
async def not_found_exception_handler(request: Request, exc: NotFoundException):
return JSONResponse(
status_code=404, content={"message": f"User with ID {exc.user_id} not found"}
)
@app.get("/users/{user_id}")
async def find_user(user_id: str):
if user_id == "not_found":
raise NotFoundException(user_id=user_id)
return {"user_id": user_id}
위 코드는 NotFoundException
을 처리하며, user_id
가 not_found일 때 404 상태 코드와 함께 구체적인 오류 메시지를 반환한다.
응답
{
"message": "Item with ID not_found not found"
}
5. 입력값 오류 처리하기
아래에서 user_id는 필수값이며 공백으로 실행하면 아래와 같이 오류가 발생한다.
@app.get("/users")
async def find(
user_id: str,
):
return user_id
응답
HTTP/1.1 422 Unprocessable Entity
{
"detail": [
{
"type": "missing",
"loc": [
"query",
"user_id"
],
"msg": "Field required",
"input": null
}
]
}
입력값 오류(ValueError)를 재정의 하려면 RequestValidationError
를 처리하면 된다.
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
return JSONResponse(
status_code=422, content={"message": f"Value Error", "detail": str(exc)}
)
응답
HTTP/1.1 422 Bad Request
{
"message": "Value Error",
"detail": "[{'type': 'missing', 'loc': ('query', 'user_id'), 'msg': 'Field required', 'input': None}]"
}
6. custom exception을 별도 파일로 관리하기
위의 예제에서 4가지의 exception handler를 따로 정의를 하였다.
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
...
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
...
@app.exception_handler(NotFoundException)
async def not_found_exception_handler(request: Request, exc: NotFoundException):
...
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc: RequestValidationError):
...
fastapi의 main.py에서 @app.exception_handler
decorator로 관리하다 보면 exception의 종류가 많아져 파일이 복잡해진다. 이때 exception 관련 처리만 별도의 파일로 관리하면 좋다.
아래와 같이 add_exception_handler
로 custom exception
을 추가한다. 그리고 기존에 정의한 exception은 custom_exception_handlers.py
에 둔다.
[main.app]
app = FastAPI()
app.add_exception_handler(CustomException, custom_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(NotFoundException, not_found_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
[custom_exception_handlers.py]
from urllib.request import Request
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from fastapi import HTTPException
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
class NotFoundException(Exception):
def __init__(self, user_id: str):
self.user_id = user_id
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=400,
content={"message": f"An error occurred: {exc.name}"},
)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code, content={"detail": f"Oops! {exc.detail}"}
)
async def not_found_exception_handler(request: Request, exc: NotFoundException):
return JSONResponse(
status_code=404, content={"message": f"User with ID {exc.user_id} not found"}
)
async def validation_exception_handler(request, exc: RequestValidationError):
return JSONResponse(
status_code=400, content={"message": f"Value Error", "detail": str(exc)}
)