나는 이렇게 학습한다/Framework

FastAPI _ Custom Exception 만드는 방법

daco2020 2022. 5. 4. 23:45
반응형

코드를 작성하다 보면 특정 상황에 대한 예외처리를 만들고 싶을 때가 있다.

 

FastAPI는 일반적으로 HTTPException을 이용해 예외를 처리하는데,

유사한 예외를 반복적으로 처리해야 한다면 우리가 직접 Exception을 커스텀해서 사용할 수 있다.

 

 

이번 글에서는 FastAPI에서 Custom Exception을 어떻게 구현하는지 코드로 설명해보겠다.

 

 

 

 

구현 시작

먼저 아주 간단한 api를 작성해보았다.

from fastapi import FastAPI


app = FastAPI()


@app.get("/{name}")
def home(name:str):
    return {"detail": f"Hello, {name}"}

name을 쿼리 스트링으로 넘겨주면 "Hello, {name}"으로 반환해주는 api다.

 

 

 

하지만 name이 다섯 글자를 초과해서는 안된다고 가정해보자.

 

그럼 다음처럼 구현할 수 있을 것이다.

from fastapi import FastAPI, HTTPException


app = FastAPI()


@app.get("/{name}")
def home(name:str):
    if len(name) > 5:
        raise HTTPException(status_code=400, detail="invalid name")
    else:
        return {"detail": f"Hello, {name}"}

 

 

현재는 이것만으로도 충분하지만 만약 유사한 예외들이 많아진다면 하나로 묶는 게 더 편할 수 있다.

 

FastAPI에서는 클래스를 만들어 개발자가 유연하게 예외처리를 할 수 있도록 도와준다.

 

 

 

 

Exception 클래스 구현

방법은 아주 간단하다.

 

class InvalidName(Exception):
    pass

새롭게 만들고자 하는 예외처리 클래스에 Exception을 상속받는다.

 

그리고,

 

끝이다.

 

 

 

이제 해당 클래스를 raise로 호출하면 except로 잡을 수 있다.

@app.get("/{name}")
def home(name:str):
    try:
        if len(name) > 5:
            raise InvalidName
        else:
            return {"detail": f"Hello, {name}"}
        
    except InvalidName:
        raise HTTPException(status_code=400, detail=f"{name} is too long")

이것으로 나만의 예외 클래스를 만들었다! 야호~

 

 

 

 

기본값 메시지 지정

예외처리 클래스에 기본 메시지를 지정해 줄 수도 있다.

 

다음처럼 클래스 내에 생성자를 두면, 이후 속성을 호출하여 사용할 수 있다.

class InvalidName(Exception):
    def __init__(self, detail: str = "invalid name"):
        self.detail = detail
        
        
@app.get("/{name}")
def home(name:str):
    try:
        if len(name) > 5:
            raise InvalidName
        else:
            return {"detail": f"Hello, {name}"}
        
    except InvalidName as error:
        raise HTTPException(status_code=400, detail=error.detail)

 

위 코드는 raise InvalidName 호출과 동시에 "invalid name"이라는 기본 메시지가 detail 변수에 할당되고

 

except에서 error.detail처럼 속성으로 불러와 사용할 수 있다. 

 

 

 

 

사용자 메시지 지정

또한 내가 원하는 메시지를 직접 지정할 수도 있다.

@app.get("/{name}")
def home(name:str):
    try:
        if len(name) > 5:
            raise InvalidName(f"{name} invalid name")
        else:
            return {"detail": f"Hello, {name}"}
        
    except InvalidName as error:
        raise HTTPException(status_code=400, detail=error.detail)

이제 메시지의 기본값은 f"{name} invalid name"로 대체되어 클라이언트로 반환한다.

 

 

 

 

마무리

예외처리를 커스텀할 수 있는 건 알겠는데 코드는 오히려 더 복잡해졌다?

사실 위의 코드는 사용자 예외처리가 적합하지 않다.

 

 

처음에 언급한 것처럼 단일 케이스보다는

유사한 예외 케이스가 많고, 유연하게 컨트롤할 수 있어야 할 때,

그때 Custom Exception는 큰 위력을 발휘한다.

 

 

아래 코드를 보자.

실습 코드에서 유효성 검사 함수를 추가했다.

@app.get("/{name}")
def home(name:str):
    try:
        validation_name(name)
        return {"detail": f"Hello, {name}"}
        
    except InvalidName as error:
        raise HTTPException(status_code=400, detail=error.detail)
    
    
    
def validation_name(name):
    if len(name) > 5:
        raise InvalidName
    if len(name) < 2:
        raise InvalidName
    if not name.isalpha():
        raise InvalidName("not a string")

세 가지 항목에 대해 검사를 하는데 단순히 InvalidName를 raise 하는 것만으로 반복되는 예외 케이스들을 처리할 수 있다.

 

메시지는 기본값으로 반환되고, 필요한 경우에만 직접 메시지를 지정할 수도 있다.

 

 

 

덧붙이자면, 메시지 기본값 외에도 다양한 조건과 연산들을 클래스 내에 추가하여 예외를 처리할 수 있다.

앞으로는 Custom Exception으로 즐겁게 예외 핸들링을 해보자 :)

 

 

 

반응형