본문 바로가기

Backend/FastAPI

FastAPI에서 예외처리하기

Errors and Exceptions in Python

기본적으로 Python에서 설명하는 Error와 Exception은 다르다. Error에는 두가지 종류가 있다.

https://docs.python.org/3/tutorial/errors.html

1. Systax Error

말 그대로 syntax error이다.

2. Exeption

실행 중에 감지된 error이다.

그래서 우리가 앞으로 사용할 Exception은 아래 Python exceptiopn hierarchy에서 Exception을 상속받아 사용한다.

https://docs.python.org/2/library/exceptions.html#exception-hierarchy

Exception을 상속받은 클래스의 이름은 Error로 끝난다. 이에 대해서는 다음을 참고하면 좋을 것 같다.

https://stackoverflow.com/questions/60708789/exceptions-vs-errors-in-python

 

Starlette Middleware

Starlette application를 사용하면 exception handler로 감싸진 ASGI middleware를 추가할 수 있다.

모든 Starlette application은 두 middleware를 기본으로 가지고 있다.

  • ServerErrorMiddleware - application exception이 500을 return하는 것을 보장한다. 그리고 DEBUG mode에서 application traceback을 display하는 것을 보장한다. 언제나 middleware layer의 가장 위에 있다.
  • ExceptionMiddleware - exception handler를 추가해 특정 exception case가 handler function과 연결되도록 할 수 있다. 예를 들어, HTTPException(status_code=404)를 raise하면, 404 page를 rendering한다.

Middleware는 top-to-bottom으로 진행된다. 어플리케이션의 실행 flow는 다음과 같다.

  • Middleware
    • ServerErrorMiddleware
    • TrustedHostMiddleware
    • HTTPSRedirectMiddleware
    • ExceptionMiddleware
  • Routing
  • Endpoint

 

Starlette Middleware의 Errors와 Exceptions

starlette의 문서를 참고해서 작성했다.

https://www.starlette.io/exceptions/

handled exceptions와 errors는 다르다.

1. Handled Exceptions

error cases가 아니다.
standard middleware stack을 통해 적절한 HTTP response를 내보낸다.
기본적으로 HTTPException class 가 사용된다.
미들웨어의 ExceptionMiddleware에서 처리된다.

2. Errors

errors는 다른 모든 exception이다. 즉, Unhandled Exceptions이다.
에러들은 미들웨어 스택을 하나씩 타고 올라간다.

 

FastAPI HttpException

Starlette의 HttpException에 헤더만 추가한 클래스이다. 따라서 Handled Exceptions이다.

 

Custom Error를 정의하고 Exception Handler를 적용시키기

방법1. decorator 활용하기

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

app = FastAPI()

@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

방법2. 앱 안에 주입하기

from starlette.applications import Starlette
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse

HTML_404_PAGE = ...
HTML_500_PAGE = ...

async def not_found(request: Request, exc: HTTPException):
    return HTMLResponse(content=HTML_404_PAGE, status_code=exc.status_code)

async def server_error(request: Request, exc: HTTPException):
    return HTMLResponse(content=HTML_500_PAGE, status_code=exc.status_code)

exception_handlers = {
    404: not_found,
    500: server_error
}

app = Starlette(routes=routes, exception_handlers=exception_handlers)

 

그래도 무슨 말인지 모르겠어서 Error(Unhandled Exception)을 발생시켜봤다

그랬더니 실행 순서는 다음과 같았다.

endpointRouterAsyncExitStackMiddlewareExceptionMiddlewareServerErrorMiddleware

 

결론

엔드포인트에서 에러가 나면 에러를 한단계씩 위로 올린다. 따라서 미들웨어에서도 아래에서부터 올리는 것이다. 그래서 Handeld Exception은 ExceptionMiddleware 에서 걸리고, Unhandled Exception, 즉 error는 ServerErrorMiddleware 에서 걸리게 된다.

'Backend > FastAPI' 카테고리의 다른 글

MongoEngine Document to Pydantic (ObjectId 처리하기)  (0) 2023.02.28