[DRF] Exceptions

강민성·2024년 10월 1일

DRF API Guide

목록 보기
25/28

예외 처리

예외 처리는... 프로그램 구조의 중앙 또는 상위 단계에서 에러 처리를 깔끔하게 조직할 수 있게 해줍니다.
— Doug Hellmann, Python Exception Handling Techniques

REST 프레임워크 뷰의 예외 처리

REST 프레임워크의 뷰는 다양한 예외를 처리하고 적절한 오류 응답을 반환합니다.

처리되는 예외는 다음과 같습니다:

  • REST 프레임워크 내부에서 발생한 APIException의 하위 클래스
  • Django의 Http404 예외
  • Django의 PermissionDenied 예외

각 경우에 REST 프레임워크는 적절한 상태 코드와 콘텐츠 유형을 포함한 응답을 반환합니다. 응답 본문에는 오류의 성격에 대한 추가적인 세부 정보가 포함될 수 있습니다.

대부분의 오류 응답은 응답 본문에 detail이라는 키를 포함합니다.

예를 들어, 다음 요청이 있을 경우:

DELETE http://api.example.com/foo/bar HTTP/1.1
Accept: application/json

이 리소스에서 DELETE 메서드가 허용되지 않음을 나타내는 오류 응답을 받을 수 있습니다:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 42

{"detail": "Method 'DELETE' not allowed."}

검증 오류는 약간 다르게 처리되며, 필드 이름이 응답의 키로 포함됩니다. 특정 필드와 관련이 없는 검증 오류인 경우, "non_field_errors" 키 또는 NON_FIELD_ERRORS_KEY 설정에서 지정된 문자열 값을 사용합니다.

검증 오류의 예는 다음과 같습니다:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 94

{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}

커스텀 예외 처리

API 뷰에서 발생한 예외를 응답 객체로 변환하는 핸들러 함수를 만들어 커스텀 예외 처리를 구현할 수 있습니다. 이를 통해 API에서 사용되는 오류 응답의 스타일을 제어할 수 있습니다.

이 함수는 두 개의 인자를 받아야 하며, 첫 번째 인자는 처리할 예외이고 두 번째 인자는 현재 처리 중인 뷰와 같은 추가 컨텍스트 정보를 포함하는 딕셔너리입니다. 예외 핸들러 함수는 Response 객체를 반환하거나, 처리할 수 없는 경우 None을 반환해야 합니다. 핸들러가 None을 반환하면 예외가 다시 발생되며, Django는 기본 HTTP 500 '서버 오류' 응답을 반환합니다.

예를 들어, 모든 오류 응답에 HTTP 상태 코드를 포함하고 싶다면 다음과 같은 응답을 보낼 수 있습니다:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json
Content-Length: 62

{"status_code": 405, "detail": "Method 'DELETE' not allowed."}

응답 스타일을 변경하려면 다음과 같은 커스텀 예외 핸들러를 작성할 수 있습니다:

from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # REST 프레임워크의 기본 예외 핸들러를 먼저 호출하여 표준 오류 응답을 가져옵니다.
    response = exception_handler(exc, context)

    # 이제 HTTP 상태 코드를 응답에 추가합니다.
    if response is not None:
        response.data['status_code'] = response.status_code

    return response

기본 핸들러는 context 인자를 사용하지 않지만, 예외 핸들러에서 처리 중인 뷰에 대한 정보가 필요한 경우 context['view']를 통해 접근할 수 있어 유용할 수 있습니다.

예외 핸들러는 설정에서 EXCEPTION_HANDLER 설정 키를 사용해 구성해야 합니다. 예를 들어:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}

지정되지 않은 경우, EXCEPTION_HANDLER 설정은 REST 프레임워크가 제공하는 기본 예외 처리기로 기본 설정됩니다:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}

예외 핸들러는 예외로 인해 생성된 응답에 대해서만 호출됩니다. 뷰에서 직접 반환된 응답에는 사용되지 않습니다. 예를 들어, 일반 뷰가 직렬화 검증 실패 시 반환하는 HTTP_400_BAD_REQUEST 응답에는 적용되지 않습니다.

API 레퍼런스

APIException

형식: APIException()

APIView 클래스 또는 @api_view 내에서 발생하는 모든 예외의 기본 클래스입니다.

커스텀 예외를 제공하려면 APIException을 서브클래싱하고 클래스에 .status_code, .default_detail, .default_code 속성을 설정하세요.

예를 들어, API가 가끔 도달할 수 없는 제3자 서비스에 의존하는 경우, "503 서비스 이용 불가" HTTP 응답 코드를 위한 예외를 구현할 수 있습니다. 다음과 같이 할 수 있습니다:

from rest_framework.exceptions import APIException

class ServiceUnavailable(APIException):
    status_code = 503
    default_detail = 'Service temporarily unavailable, try again later.'
    default_code = 'service_unavailable'

API 예외 검사

API 예외의 상태를 검사하기 위해 사용할 수 있는 여러 속성이 있습니다. 이를 사용해 프로젝트의 커스텀 예외 처리를 구축할 수 있습니다.

사용 가능한 속성과 메서드는 다음과 같습니다:

  • .detail - 오류의 텍스트 설명을 반환합니다.
  • .get_codes() - 오류의 코드 식별자를 반환합니다.
  • .get_full_details() - 텍스트 설명과 코드 식별자 모두를 반환합니다.

대부분의 경우 오류 세부 사항은 단일 항목일 것입니다:

>>> print(exc.detail)
You do not have permission to perform this action.
>>> print(exc.get_codes())
permission_denied
>>> print(exc.get_full_details())
{'message': 'You do not have permission to perform this action.', 'code': 'permission_denied'}

검증 오류의 경우 오류 세부 사항은 항목 목록 또는 딕셔너리일 수 있습니다:

>>> print(exc.detail)
{"name": "This field is required.", "age": "A valid integer is required."}
>>> print(exc.get_codes())
{"name": "required", "age": "invalid"}
>>> print(exc.get_full_details())
{"name": {"message": "This field is required.", "code": "required"}, "age": {"message": "A valid integer is required.", "code": "invalid"}}

ParseError

형식: ParseError(detail=None, code=None)

request.data에 접근할 때 요청에 잘못된 데이터가 포함된 경우 발생합니다.

기본적으로 이 예외는 "400 Bad Request" HTTP 상태 코드를 반환하는 응답을 생성합니다.

AuthenticationFailed

형식: AuthenticationFailed(detail=None, code=None)

잘못된 인증 정보를 포함한 요청이 들어온 경우 발생합니다.

기본적으로 이 예외는 "401 Unauthenticated" HTTP 상태 코드를 반환하는 응답을 생성하지만, 사용 중인 인증 방식에 따라 "403 Forbidden" 응답을 반환할 수도 있습니다. 자세한 내용은 인증 문서를 참조하세요.

NotAuthenticated

형식: NotAuthenticated(detail=None, code=None)

인증되지 않은 요청이 권한 확인을 통과하지 못한 경우 발생합니다.

기본적으로 이 예외는 "401 Unauthenticated" HTTP 상태 코드를 반환하는 응답을 생성하지만, 사용 중인 인증 방식에 따라 "403 Forbidden" 응답을 반환할 수도 있습니다. 자세한 내용은 인증 문서를 참조하세요.

PermissionDenied

형식: PermissionDenied(detail=None, code=None)

인증된 요청이 권한 확인을 통과하지 못한 경우 발생합니다.

기본적으로 이 예외는 "403 Forbidden" HTTP 상태 코드를 반환하는 응답을 생성합니다.

NotFound

형식: NotFound(detail=None, code=None)

주어진 URL에서 리소스가 존재하지 않을 때 발생합니다. 이 예외는 Django의 표준 Http404 예외와 동일합니다.

기본적으로 이 예외는 "404 Not Found" HTTP 상태 코드를 가진 응답을 반환합니다.

MethodNotAllowed

형식: MethodNotAllowed(method, detail=None, code=None)

뷰에서 처리할 수 없는 메서드로 요청이 들어왔을 때 발생합니다.

기본적으로 이 예외는 "405 Method Not Allowed" HTTP 상태 코드를 가진 응답을 반환합니다.

NotAcceptable

형식: NotAcceptable(detail=None, code=None)

Accept 헤더가 지원 가능한 렌더러 중 어느 것도 만족시키지 못하는 요청이 들어왔을 때 발생합니다.

기본적으로 이 예외는 "406 Not Acceptable" HTTP 상태 코드를 가진 응답을 반환합니다.

UnsupportedMediaType

형식: UnsupportedMediaType(media_type, detail=None, code=None)

request.data를 처리할 수 있는 파서가 없을 때 발생합니다.

기본적으로 이 예외는 "415 Unsupported Media Type" HTTP 상태 코드를 가진 응답을 반환합니다.

Throttled

형식: Throttled(wait=None, detail=None, code=None)

요청이 제한(throttling) 검사를 통과하지 못했을 때 발생합니다.

기본적으로 이 예외는 "429 Too Many Requests" HTTP 상태 코드를 가진 응답을 반환합니다.

ValidationError

형식: ValidationError(detail=None, code=None)

ValidationError 예외는 다른 APIException 클래스들과 약간 다릅니다:

  • detail 인수는 오류 세부 정보의 리스트 또는 딕셔너리가 될 수 있으며, 중첩된 데이터 구조일 수도 있습니다. 딕셔너리를 사용하면, serializervalidate() 메서드에서 객체 수준 검증을 수행할 때 필드 수준 오류를 지정할 수 있습니다. 예를 들어, raise serializers.ValidationError({'name': '유효한 이름을 입력해주세요.'})와 같이 사용합니다.

  • Django의 내장된 ValidationError와 구별하기 위해 serializers 모듈을 임포트하고 완전한 경로를 사용하여 ValidationError를 호출하는 것이 관례입니다. 예: raise serializers.ValidationError('이 필드는 정수여야 합니다.').

  • ValidationError 클래스는 serializer와 필드 검증에 사용되며, 검증자 클래스에서도 사용됩니다. 또한 serializer.is_validraise_exception=True 인수와 함께 호출할 때 발생합니다.

    serializer.is_valid(raise_exception=True)
  • 일반적인 뷰는 raise_exception=True 플래그를 사용하므로, API의 검증 오류 응답 스타일을 전역적으로 재정의할 수 있습니다. 이를 위해서는 앞서 설명한 대로 사용자 정의 예외 처리기를 사용하십시오.

기본적으로 이 예외는 "400 Bad Request" HTTP 상태 코드를 가진 응답을 반환합니다.

Generic Error Views

Django REST Framework는 JSON 형식의 500 서버 오류와 400 잘못된 요청 응답을 제공하는 두 가지 일반 오류 뷰를 제공합니다. (Django의 기본 오류 뷰는 HTML 형식의 응답을 제공하므로, API 전용 애플리케이션에는 적합하지 않을 수 있습니다.)

Django의 Customizing error views 문서에 따라 사용하십시오.

rest_framework.exceptions.server_error

500 상태 코드와 application/json 콘텐츠 유형을 가진 응답을 반환합니다.

handler500으로 설정합니다:

handler500 = 'rest_framework.exceptions.server_error'

rest_framework.exceptions.bad_request

400 상태 코드와 application/json 콘텐츠 유형을 가진 응답을 반환합니다.

handler400으로 설정합니다:

handler400 = 'rest_framework.exceptions.bad_request'

외부 패키지

다음의 외부 패키지도 사용할 수 있습니다.

DRF Standardized Errors

drf-standardized-errors 패키지는 4xx 및 5xx 응답에 대해 동일한 형식을 생성하는 예외 핸들러를 제공합니다. 이는 기본 예외 핸들러를 대체하는 방식으로, 예외 핸들러를 전체적으로 다시 작성하지 않고도 오류 응답 형식을 커스텀할 수 있습니다. 표준화된 오류 응답 형식은 API를 사용하는 사람이 처리하거나 문서화하기에 더 쉽습니다.

Reference

DRF API Guide - Exceptions

profile
Back-end Junior Developer

0개의 댓글