Django REST Framework/DRF 일반

[DRF] 공식 문서 - Exceptions 정리

bluebamus 2024. 1. 22. 16:12

 1. Exceptions

   1) REST 프레임워크 뷰에서의 예외 처리(Exception handling in REST framework views)

      - REST 프레임워크의 뷰는 다양한 예외를 처리하며, 적절한 오류 응답을 반환하는 것을 처리한다.
      - 처리되는 예외는 다음과 같다.
         - REST 프레임워크 내부에서 발생하는 APIException의 서브클래스.
         - Django의 Http404 예외.
         - Django의 PermissionDenied 예외.


      - 각 경우에, REST 프레임워크는 적절한 상태 코드와 content-type을 가진 응답을 반환한다. 응답의 본문에는 오류의 성격에 대한 추가적인 세부 사항이 포함된다.

      - 대부분의 오류 응답은 응답의 본문에 키 "detail"을 포함한다.

      - 예를 들어, 다음 request에서

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."]}

 

   2) 사용자 정의 예외 처리(Custom exception handling)

      - 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):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    # Now add the HTTP status code to the response.
    if response is not None:
        response.data['status_code'] = response.status_code

    return response

 

      - context 인수는 기본 핸들러에서 사용되지 않지만, 예외 핸들러가 현재 처리되고 있는 뷰와 같은 추가 정보가 필요한 경우 유용할 수 있다. 이는 context['view']로 접근할 수 있다.

 

      - 예시

from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    # Now add the HTTP status code to the response.
    if response is not None:
        response.data['status_code'] = response.status_code

    return response


# custom_exception_handler를 사용하는 가상의 시나리오에 맞는 코드 예시입니다.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

# 가상의 시나리오에 맞춰 APIView를 상속받은 클래스를 정의합니다.
class CustomView(APIView):
    def get(self, request):
        try:
            # 가상의 시나리오에 맞는 예외 발생
            raise Exception("가상의 예외가 발생했습니다.")
        except Exception as e:
            # 예외를 잡아서 처리합니다.
            data = {'error': str(e)}
            return custom_exception_handler(e, self)


      - 예외 처리기는 또한 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 응답과 같은 뷰에서 직접 반환된 모든 응답에는 사용되지 않는다.

 

 2. API Reference

   1) APIException

      - Signature: APIException()

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

      - 사용자 정의 예외를 제공하려면 APIException을 상속받아 클래스에서 .status_code, .default_detail, .default_code 속성을 설정하면 된다.

      - 예를 들어, API가 때때로 접근할 수 없는 제3의 서비스에 의존하는 경우, "503 Service Unavailable" 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'

 

      - 예시

from rest_framework.views import exception_handler
from rest_framework.exceptions import APIException
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

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

class CustomView(APIView):
    def get(self, request):
        try:
            # 가상의 시나리오에 맞는 예외 발생
            raise ServiceUnavailable()
        except ServiceUnavailable as e:
            # 예외를 잡아서 처리합니다.
            data = {'error': str(e)}
            return Response(data, status=e.status_code)

 

      1. Inspecting API exceptions

         - 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"}}

 

   2) ParseError

      - Signature: ParseError(detail=None, code=None)

      - 요청이 request.data에 접근할 때 잘못된 데이터를 포함하고 있다면 발생한다.

      - 기본적으로 이 예외는 "400 Bad Request"라는 HTTP 상태 코드를 가진 응답을 전달한다.

 

   3) AuthenticationFailed

      - Signature: AuthenticationFailed(detail=None, code=None)

      - 들어오는 요청이 잘못된 인증을 포함하고 있을 때 발생한다.

      - 기본적으로 이 예외는 "401 Unauthenticated"라는 HTTP 상태 코드를 가진 응답을 전달한다. 하지만 사용 중인 인증 체계에 따라 "403 Forbidden" 응답을 전달 할 수도 있다. 자세한 내용은 인증 문서를 참조하면 된다.

         - authentication documentation : https://www.django-rest-framework.org/api-guide/authentication/

 

   4) NotAuthenticated

      - Signature: NotAuthenticated(detail=None, code=None)

      - 인증되지 않은 요청이 권한 검사에 실패했을 때 발생한다.

      - 기본적으로 이 예외는 "401 Unauthenticated"라는 HTTP 상태 코드를 가진 응답을 결과로 한다. 하지만 사용 중인 인증 체계에 따라 "403 Forbidden" 응답을 결과로 할 수도 있다. 자세한 내용은 인증 문서를 참조하면 된다.

      - 인증되지 않은 요청이 권한 검사에 실패했을 때 발생한다.

      - 기본적으로 이 예외는 "401 Unauthenticated"라는 HTTP 상태 코드를 가진 응답을 결과로 한다. 하지만 사용 중인 인증 체계에 따라 "403 Forbidden" 응답을 결과로 할 수도 있다. 자세한 내용은 인증 문서를 참조하면 된다.

         - authentication documentation : https://www.django-rest-framework.org/api-guide/authentication/

 

   5) PermissionDenied

      - Signature: PermissionDenied(detail=None, code=None)

      - 인증된 요청이 권한 검사에 실패했을 때 발생한다.
      - 기본적으로 이 예외는 "403 Forbidden"이라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   6) NotFound

      - Signature: NotFound(detail=None, code=None)

      - 주어진 URL에서 리소스가 존재하지 않을 때 발생합니다. 이 예외는 표준 Http404 Django 예외와 동일하다.
      - 기본적으로 이 예외는 "404 Not Found"라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   7) MethodNotAllowed

      - Signature: MethodNotAllowed(method, detail=None, code=None)

      - 들어오는 요청이 뷰의 핸들러 메소드에 매핑되지 않을 때 발생한다.
      - 기본적으로 이 예외는 "405 Method Not Allowed"라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   8) NotAcceptable

      - Signature: NotAcceptable(detail=None, code=None)

      - 들어오는 요청이 사용 가능한 렌더러 중 어떤 것도 만족시키지 못하는 Accept 헤더를 가질 때 발생한다.
      - 기본적으로 이 예외는 "406 Not Acceptable"이라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   9) UnsupportedMediaType

      - Signature: UnsupportedMediaType(media_type, detail=None, code=None)

      - 요청이 request.data에 접근할 때 요청 데이터의 콘텐츠 유형을 처리할 수 있는 파서가 없다면 발생한다.
      - 기본적으로 이 예외는 "415 Unsupported Media Type"이라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   10) Throttled

      - Signature: Throttled(wait=None, detail=None, code=None)

      - 들어오는 요청이 스로틀링 검사에 실패했을 때 발생한다.
      - 기본적으로 이 예외는 "429 Too Many Requests"라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

   11) ValidationError

      - Signature: ValidationError(detail=None, code=None)

      - ValidationError 예외는 다른 APIException 클래스들과 약간 다르다.
         - detail 인자는 오류 세부 정보의 리스트 또는 사전이며, 중첩된 데이터 구조일 수도 있다. 사전을 사용하면, serializer의 validate() 메소드에서 객체 수준의 유효성 검사를 수행하면서 필드 수준의 오류를 지정할 수 있다. 예를 들어, raise serializers.ValidationError({'name': '올바른 이름을 입력해주세요.'})

         - 관례적으로, Django의 내장 유효성 검사 오류와 구별하기 위해 serializers 모듈을 임포트하고 완전히 자격을 갖춘 ValidationError 스타일을 사용해야 한다. 예를 들어, raise serializers.ValidationError('이 필드는 정수 값이어야 합니다.')


      - ValidationError 클래스는 serializer와 필드 유효성 검사, 그리고 validator 클래스에서 사용되어야 한다. 또한, serializer.is_valid를 raise_exception 키워드 인자와 함께 호출할 때 발생한다:

serializer.is_valid(raise_exception=True)

 

      - 일반 뷰는 raise_exception=True 플래그를 사용한다. 이는 API에서 전역적으로 유효성 검사 오류 응답의 스타일을 재정의할 수 있음을 의미한다. 이렇게 하려면 위에서 설명한 것처럼 사용자 정의 예외 핸들러를 사용하면 된다.

      - 기본적으로 이 예외는 "400 Bad Request"라는 HTTP 상태 코드를 가진 응답을 전송한다.

 

 3. Generic Error Views

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

   - 이를 사용하려면 Django의 오류 뷰 커스터마이징 문서를 참조하면 된다.

      - Django's Customizing error views documentation : https://docs.djangoproject.com/en/5.0/topics/http/views/#customizing-error-views

 

   1) rest_framework.exceptions.server_error

      - 상태 코드 500과 application/json 콘텐트 타입을 가진 응답을 반환한다.
      - handler500으로 설정한다.

handler500 = 'rest_framework.exceptions.server_error'

 

   2) rest_framework.exceptions.bad_request

      - 상태 코드 400과 application/json 콘텐트 타입을 가진 응답을 반환한다.
      - handler400으로 설정하십시오:

handler400 = 'rest_framework.exceptions.bad_request'

 

 4. Third party packages

   1) DRF 표준화된 오류(DRF Standardized Errors)

      - drf-standardized-errors 패키지는 모든 4xx와 5xx 응답에 대해 동일한 형식을 생성하는 예외 핸들러를 제공한다. 이는 기본 예외 핸들러를 대체하는 것으로, 전체 예외 핸들러를 다시 작성하지 않고도 오류 응답 형식을 커스터마이징할 수 있다. 표준화된 오류 응답 형식은 문서화하기 쉽고 API 사용자가 처리하기 쉽다.

         - drf-standardized-errors : https://github.com/ghazi-git/drf-standardized-errors

 

      - 지원하는 기능:
         - 오류 추가: add_error(field, message) 메서드를 사용하여 필드와 오류 메시지를 추가할 수 있다. 이를 통해 필드별로 오류를 정의할 수 있다.
         - 오류 응답 생성: get_error_response() 메서드를 호출하여 표준화된 오류 응답을 반환할 수 있다. 이 메서드는 serializer.errors와 같은 형식의 오류 정보를 표준화된 형태로 변환하여 응답다.
         - 사용자 정의 유효성 검사: StandardizedErrorsMixin을 상속받은 뷰에서 사용자 정의 유효성 검사 로직을 구현할 수 있습니다. 필요한 경우, 사용자는 필드별로 유효성 검사를 수행하고, 오류를 추가할 수 있다.
         - 오류 메시지 다국어 지원: drf-standardized-errors는 다국어 지원을 제공한다. 오류 메시지를 다국어로 정의하고, 클라이언트의 언어 설정에 따라 적절한 오류 메시지를 반환할 수 있다.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from drf_standardized_errors.mixins import StandardizedErrorsMixin

class UserRegistrationView(StandardizedErrorsMixin, APIView):
    def post(self, request):
        data = request.data

        # 필수 필드 검사
        if 'username' not in data:
            self.add_error('username', '사용자명은 필수 항목입니다.')
        if 'password' not in data:
            self.add_error('password', '비밀번호는 필수 항목입니다.')

        # 사용자명 중복 검사
        if 'username' in data and self.check_duplicate_username(data['username']):
            self.add_error('username', '이미 사용 중인 사용자명입니다.')

        if self.errors:
            return self.get_error_response()

        # 회원 가입 로직
        # ...

        return Response({'message': '회원 가입이 완료되었습니다.'}, status=status.HTTP_201_CREATED)

    def check_duplicate_username(self, username):
        # 사용자명 중복 검사 로직을 구현합니다.
        # 중복되는 사용자명인 경우 True를 반환하고, 그렇지 않은 경우 False를 반환합니다.
        # ...

 

 - 공식 사이트 문서 : https://www.django-rest-framework.org/api-guide/exceptions/

 

Exceptions - Django REST framework

 

www.django-rest-framework.org