Exception
어플리케이션을 만들 때 잘 동작하는 경우만 생각하는 실수를 범하기 쉽다. 예를 들어, 로그인 화면에서 이메일을 입력하라고 해도 이메일을 입력하지 않는 사람도 많다. 또는 숫자를 입력해야하는 곳에 문자열을 입력하거나, 계좌이체를 할 때 계좌 잔액보다 많은 금액을 입력하는 경우도 발생할 수 있다. 개발자는 당연하다고 생각한 것들이 사용자들에게는 아닐 수 있다. 혹은 악의적인 의도를 가지고 이상한 값을 입력하는 경우도 많다. 이유가 어찌됐든 이러한 예외적인 상황을 생각하고 대비해야 좋은 서비스를 만들 수 있다. 이번 글에서는 FastAPI에서 이러한 예외 상황을 처리하는 방법을 알아보도록 한다.
HTTPException
가장 간단한 방법은 HTTPException을 사용하는 것이다. HTTPException은 python의 built-in 클래스인 Exception을 상속받은 클래스로, status code와 메시지, HTTP 헤더 등을 커스텀할 수 있다.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
위 예시와 같이 간단히 사용할 수 있다. item_id로 abc를 입력한 경우에는 아래와 같은 응답을 받을 수 있다.
{"detail":"Item not found"}
만약 HTTP 헤더를 이용해 추가적인 로직을 구현한다면, 아래와 같이 헤더를 추가할 수 있다.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"},
)
return {"item": items[item_id]}
Custom Exception
HTTPException은 사용하기 간단하지만, 예외가 발생하는 곳에서 메시지와 status code 등을 직접 입력하는 것은 중복된 코드가 발생하기 쉽고, 유지보수성이 떨어질 것이다. 따라서 자주 발생하는 Exception에 대해 Custom Exception 클래스를 만들어 재사용하는 것이 좋다.
class CustomException(Exception):
def __init__(self, name: str):
self.name = name
위 예시에서는 built-in Exception을 직접 상속받아 name이라는 속성을 갖도록 만들었다. 다음으로 만들 Exception Handler를 추가하면 이 속성을 마음대로 사용할 수 있기 때문에 message와 같은 속성을 추가할 수도 있다.
@app.get("/custom/{name}")
async def custom_exception(name: str):
raise CustomException(name=name)
CustomException은 일반적인 Exception과 똑같이 사용하면 된다.
ExceptionHandler
HTTPException과 달리 위의 CustomException이 일어나면 status code나 메시지가 나오지 않는다. 어떠한 Exception을 정의한다고 해도 항상 status code는 500이고, 구체적인 메시지를 볼 수 없다. 이것은 CustomException이 일어났을 때 처리해주는 Handler가 없기 때문이다.
FastAPI는 Exception이 일어났을 때 적절한 Handler를 매칭해서 예외처리를 할 수 있도록 도와준다. 따라서 우리는 ExceptionHandler를 구현해서 FastAPI 객체에 등록해주기만 하면 된다. FastAPI 객체의 exception_handler 함수를 사용할 수도 있지만, 간편하게 사용하려면 아래와 같이 데코레이터를 사용할 수도 있다.
from starlette.requests import Request
from starlette.responses import JSONResponse
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=400,
content={"message": f"CustomException name : {exc.name}"}
)
참고로, import한 패키지를 보면 starlette을 볼 수 있는데, starlette은 python 비동기 프레임워크로 FastAPI가 starlette을 기반으로 만들어졌다. 따라서 FastAPI에서는 대부분의 starlette 클래스와 호환이 된다. 예를 들어, 위의 status_code를 starlette으로 수정하면 좀 더 가독성 좋은 코드를 만들 수 있다.
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"message": f"CustomException name : {exc.name}"}
)
ExceptionHandler 오버라이드
HTTPException을 사용할 때 봤던 것처럼, 몇몇 Exception은 기본적으로 제공하는 ExceptionHandler가 존재한다. 이러한 Handler를 변경하고싶다면 ExceptionHandler를 오버라이드하는 방법이 있다. 예를 들어, int 타입 파라미터가 필요한 곳에 문자 입력이 들어오는 경우, Pydantic 패키지에서 타입을 체크하여 RequestValidationError가 발생하고 아래와 같은 응답이 자동으로 반환된다.
@app.get("/items/{item_id}")
async def get_item_detail(item_id: int):
return {"id": item_id, "name": "item"}
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
RequestValidationError를 처리하는 ExceptionHandler를 아래와 같이 수정해보자.
@app.exception_handler(RequestValidationError)
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
return PlainTextResponse(str(exc), status_code=400)
이제 위와 조금 다른 응답을 확인할 수 있다.
[{'type': 'int_parsing', 'loc': ('path', 'id'), 'msg': 'Input should be a valid integer, unable to parse string as an integer', 'input': 'abc', 'url': 'https://errors.pydantic.dev/2.3/v/int_parsing'}]
위의 응답도 JSON 형태로 보이긴 하지만, 실제로 응답 헤더를 확인해보면 Content-Type이 text/plain인 것을 알 수 있다.
3줄 요약
- FastAPI는 예외를 처리할 수 있는 도구를 제공한다
- 필요에 따라 Custom Exception을 만들어서 사용할 수 있다
- Custom Exception을 처리할 수 있는 Exception Handler도 직접 만들어 사용할 수 있다
'Python > FastAPI' 카테고리의 다른 글
[FastAPI] FastAPI 튜토리얼 (9) - Background Task (0) | 2023.08.26 |
---|---|
[FastAPI] FastAPI 튜토리얼 (8) - Middleware (0) | 2023.08.12 |
[FastAPI] FastAPI 튜토리얼 (6) - 로그인 (0) | 2023.08.02 |
[FastAPI] FastAPI 튜토리얼 (5) - SQLAlchemy ORM (0) | 2023.07.30 |
[FastAPI] FastAPI 튜토리얼 (4) - SQLAlchemy Core (0) | 2023.07.22 |