Flask error handler

Chanho Yoon·2021년 5월 1일
2

Flask

목록 보기
3/3

프론트지만 Flask를 이용한 MVC 패턴으로 백엔드 API를 설계하고 있다. 우선 가장 먼저 내가 맡은 부분인 error_handler를 만들어 전체적인 에러 관리를 한 곳에서 모여 처리할 수 있도록 만들었다.

디렉터리 구조

error_handler.py

import traceback
from flask import jsonify
from flask_request_validator import *
from flask_request_validator.error_formatter import demo_error_formatter
from flask_request_validator.exceptions import InvalidRequestError, InvalidHeadersError, RuleError
from utils.custom_exception import CustomUserError
from utils.response import error_response



def error_handle(app):
    """에러 핸들러

    에러 처리하는 함수

    Args:
        app  : __init__.py에서 파라미터로 app을 전달 받은 값
    Returns:
        json : error_response() 함수로 에러 메시지를 전달해서 반환 받고 return
    """
    @app.errorhandler(Exception)
    def handle_error(e):
        traceback.print_exc()
        return error_response("서버 상에서 오류가 발생했습니다.", "Exception", 500)


    @app.errorhandler(AttributeError)
    def handle_error(e):
        traceback.print_exc()
        return error_response("서버 상에서 오류가 발생했습니다.", "NoneType Error", 500)

    @app.errorhandler(KeyError)
    def handle_key_error(e):
        traceback.print_exc()
        return error_response("데이터베이스에서 값을 가져오는데 문제가 발생하였습니다.", "Database Key Error", 500)

    @app.errorhandler(TypeError)
    def handle_type_error(e):
        traceback.print_exc()
        return error_response("데이터의 값이 잘못 입력되었습니다", "Data Type Error", 500)
 
    @app.errorhandler(ValueError)
    def handle_value_error(e):
        traceback.print_exc()
        return error_response("데이터에 잘못된 값이 입력되었습니다.", "Data Value Error", 500)
    
    # @app.errorhandler(err.OperationalError)
    # def handle_operational_error(e):
    #     traceback.print_exc()
    #     return error_response(e, "에러")

    @app.errorhandler(InvalidRequestError)
    def data_error(e):
        """validate_params 정규식 에러
        validate_params rules에 위배될 경우 발생되는 에러 메시지를 처리하는 함수
        """
        traceback.print_exc()
        dev_error_message = demo_error_formatter(
            e)[0]['errors'], demo_error_formatter(e)[0]['message']
        return error_response("형식에 맞는 값을 입력해주세요", dev_error_message, 400)

    @app.errorhandler(CustomUserError)
    def handle_error(e):
        traceback.print_exc()
        return error_response(e.error_message, e.dev_error_message, e.status_code)

먼저 error_handle 함수를 선언하고 그 안에 발생할 수 있는 에러들을 선언했다.
핵심적인 것은 마지막에 @app.errorhandler(CustomUserError) 이 부분이다.
이 부분은 말 그대로 우리가 개발하면서 발생할 수 있는 에러들을 직접 만들어 처리할 수 있도록 하는 부분이다.

그리고 view에서 아래와 같이 __init__.pyerror_handle을 등록해줘야 한다.

from .product_view import (
                            ProductView, 
                            ProductDetailView, 
                         
)

from .order_view import (
                            OrderListView,
                            OrderView
)

from .account_view import (
                            SignUpView,
                            SignInView,
                            SignInSocialView
)

from utils.error_handler import error_handle


def create_endpoints(app, services):
    product_service = services.product_service
    order_service = services.order_service
    account_service = services.account_service


    # product
    app.add_url_rule("/product/home",
                    view_func=ProductView.as_view('product_view', product_service), 
                    methods=['GET','POST', 'PATCH'])

    app.add_url_rule("/products/<product_code>", 
                    view_func=ProductDetailView.as_view('product_detail_view', product_service), 
                    methods=['GET','POST', 'PATCH'])

    # order
    app.add_url_rule("/orders",
                    view_func=OrderListView.as_view('order_list_view', order_service),
                    methods=['GET'])
   
    app.add_url_rule("/orders/<int:order_detail_number>",
                    view_func=OrderView.as_view('order_view', order_service),
                    methods=['GET'])

    # account
    app.add_url_rule("/user/signup",
                    view_func=SignUpView.as_view('signup_view', account_service),
                    methods=['POST'])
    
    app.add_url_rule("/user/signin",
                    view_func=SignInView.as_view('signin_view', account_service),
                    methods=['POST'])
    
    app.add_url_rule("/user/social",
                    view_func=SignInSocialView.as_view('signin_social_view', account_service),
                    methods=['POST'])
    
    error_handle(app)

custom_exception.py

아래와 같이 직접 코딩을 하면서 발생할 수 있는 에러들을 커스텀으로 직접 만들어 사용할 수 있게 틀을 짰다. 내가 만든 틀을 팀원이 잘 사용하는 것을 보니 괜히 뿌듯하다. (세형님의 도움도 감사합니다 😁)

'''
    dev_error_message : 개발자 에러 메시지
    error_message : 사용자 에러 메시지
    
    class errorClassName(CustomUserError):
        # parameter 설명
        # 두 번째 인자 : user error message 세 번째 인자 : dev error message 
        def __init__(self, error_message, dev_error_message):
            status_code = 500  # 에러코드
            if not dev_error_message :
                dev_error_message = "default error message"
            super().__init__(status_code, dev_error_message, error_message)
'''

from flask import jsonify
from flask_request_validator import AbstractRule
from flask_request_validator.exceptions import RuleError, RequiredJsonKeyError, RequestError

class CustomUserError(Exception):
    def __init__(self, status_code, dev_error_message, error_message):
        self.status_code = status_code
        self.dev_error_message = dev_error_message
        self.error_message = error_message

class DatabaseCloseFail(CustomUserError):
    def __init__(self, error_message, dev_error_message=None):
        status_code = 500
        if not dev_error_message:
            dev_error_message = "database.close() error"
        super().__init__(status_code, dev_error_message, error_message)

class TokenIsEmptyError(CustomUserError):
    def __init__(self, error_message):
        status_code = 400
        dev_error_message = "token is empty"
        super().__init__(status_code, dev_error_message, error_message)

class UserNotFoundError(CustomUserError):
    def __init__(self, error_message):
        status_code = 404
        dev_error_message = "user not found"
        super().__init__(status_code, dev_error_message, error_message)

class JwtInvalidSignatureError(CustomUserError):
    def __init__(self, error_message):
        status_code = 500
        dev_error_message = "signature is damaged"
        super().__init__(status_code, dev_error_message, error_message)

class JwtDecodeError(CustomUserError):
    def __init__(self, error_message):
        status_code = 500
        dev_error_message = "token is damaged"
        super().__init__(status_code, dev_error_message, error_message)

class MasterLoginRequired(CustomUserError):
    def __init__(self, error_message):
        status_code = 400
        dev_error_message = "master login required"
        super().__init__(status_code, dev_error_message, error_message)

class SellerLoginRequired(CustomUserError):
    def __init__(self, error_message):
        status_code = 400
        dev_error_message = "seller login required"

class StartDateFail(CustomUserError):
    def __init__(self,error_message):
        status_code = 400
        dev_error_message = "start_date gt end_date error"
        super().__init__(status_code, dev_error_message, error_message)

class DataNotExists(CustomUserError):
    def __init__(self, error_message, dev_error_message=None):
        status_code = 500
        if not dev_error_message:
            dev_error_message = "order status type id doesn't exist"

class IsInt(AbstractRule):
    def validate(self, value):
        if not isinstance(value, int):
            raise RuleError('invalid request')
        return value

class IsStr(AbstractRule):
    def validate(self, value):
        if not isinstance(value, str):
            raise RuleError('invalid request')
        return value

class IsFloat(AbstractRule):
    def validate(self, value):
        if not isinstance(value, float):
            raise RuleError('invalid request')
        return value

class IsRequired(AbstractRule):
    def validate(self, value):
        if not value:
            raise RuleError('invalid request')
        return value
        if not dev_error_message:
            dev_error_message = "order status type id doesn't exist"

        
class SignUpFail(CustomUserError):
    def __init__(self, error_message, dev_error_message=None):
        status_code = 400
        if not dev_error_message:
            dev_error_message = "SignUpFail error"
        super().__init__(status_code, dev_error_message, error_message)
    
class SignInError(CustomUserError):
    def __init__(self, error_message, dev_error_message=None):
        status_code = 400
        if not dev_error_message:
            dev_error_message = "SignInFaill error"
        super().__init__(status_code, dev_error_message, error_message)
        
class TokenCreateError(CustomUserError):
    def __init__(self, error_message, dev_error_message=None):
        status_code = 400
   
   if not dev_error_message:
            dev_error_message = "TokenCreate error"
        super().__init__(status_code, dev_error_message, error_message)

기본적으로 CustomUserErrpr 상속받아서 사용한다.

커스텀 에러 예시

커스텀으로 만든 error 클래스를 raise를 사용해서 error_handle로 올린다.

4개의 댓글

comment-user-thumbnail
2021년 7월 9일

오 잘 쓰겠습니다!

1개의 답글
comment-user-thumbnail
2021년 10월 31일

에러 핸들링에 많은 도움이 됐습니다
혹시, 전체 구조도 이해하고 싶은데
프로젝트 구조에 대한 기본 소스 공개가 가능하실까요??

답글 달기
comment-user-thumbnail
2022년 2월 22일

안녕하세요 잘봤습니다.
@app.errorhandler(500)
def internal_server_error(error):

# 500 에러 발생시
return render_template(ERROR_PAGE_500), 500

이렇게 처리했는데 flask웹에서는 잘처리 되는데 포스트맨을 통한 호출시 json이 없거나 하면 500에러지만 에러내용이 그대로 나오는데 어떻게 처리해야 하나요?

답글 달기